diff --git a/demo-hytradboi/src/adapter.rs b/demo-hytradboi/src/adapter.rs index 1c910e77..cc246ebc 100644 --- a/demo-hytradboi/src/adapter.rs +++ b/demo-hytradboi/src/adapter.rs @@ -12,8 +12,8 @@ use octorust::types::{ContentFile, FullRepository}; use tokio::runtime::Runtime; use trustfall::{ provider::{ - field_property, resolve_property_with, Adapter, ContextIterator, ContextOutcomeIterator, - EdgeParameters, ResolveEdgeInfo, ResolveInfo, VertexIterator, + field_property, resolve_property_with, Adapter, AsVertex, ContextIterator, + ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, VertexIterator, }, FieldValue, }; @@ -197,13 +197,13 @@ impl<'a> Adapter<'a> for DemoAdapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match (type_name.as_ref(), property_name.as_ref()) { (_, "__typename") => Box::new(contexts.map(|ctx| { let value = match ctx.active_vertex() { @@ -368,14 +368,14 @@ impl<'a> Adapter<'a> for DemoAdapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, _parameters: &EdgeParameters, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match (type_name.as_ref(), edge_name.as_ref()) { ("HackerNewsStory", "byUser") => Box::new(contexts.map(|ctx| { let vertex = ctx.active_vertex(); @@ -687,13 +687,13 @@ impl<'a> Adapter<'a> for DemoAdapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { let type_name = type_name.clone(); let coerce_to_type = coerce_to_type.clone(); let iterator = contexts.map(move |ctx| { diff --git a/pytrustfall/src/shim.rs b/pytrustfall/src/shim.rs index 224be273..6a93474e 100644 --- a/pytrustfall/src/shim.rs +++ b/pytrustfall/src/shim.rs @@ -5,7 +5,7 @@ use pyo3::{exceptions::PyStopIteration, prelude::*, wrap_pyfunction}; use trustfall_core::{ frontend::{error::FrontendError, parse}, interpreter::{ - execution::interpret_ir, Adapter, ContextIterator as BaseContextIterator, + execution::interpret_ir, Adapter, AsVertex, ContextIterator as BaseContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, ResolveInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue}, @@ -197,24 +197,49 @@ fn make_iterator(py: Python, value: Py) -> PyResult> { value.call_method(py, "__iter__", (), None) } -#[pyclass] +#[pyclass(unsendable)] #[derive(Debug, Clone)] -pub struct Context(DataContext>>); +pub(crate) struct Opaque { + data: *mut (), + pub(crate) vertex: Option>>, +} + +impl Opaque { + fn new>> + 'static>(ctx: DataContext) -> Self { + let vertex = ctx.active_vertex::>>().cloned(); + let boxed = Box::new(ctx); + let data = Box::into_raw(boxed) as *mut (); + + Self { data, vertex } + } + + /// Converts an `Opaque` into the `DataContext` it points to. + /// + /// # Safety + /// + /// When an `Opaque` is constructed, it does not store the value of the `V` generic parameter + /// it was constructed with. The caller of this function must ensure that the `V` parameter here + /// is the same type as the one used in the `Opaque::new()` call that constructed `self` here. + unsafe fn into_inner>> + 'static>(self) -> DataContext { + let boxed_ctx = unsafe { Box::from_raw(self.data as *mut DataContext) }; + *boxed_ctx + } +} #[pymethods] -impl Context { +impl Opaque { #[getter] fn active_vertex(&self) -> PyResult>> { - Ok(self.0.active_vertex().map(|arc| (**arc).clone())) + Ok(self.vertex.as_ref().map(|arc| (**arc).clone())) } } #[pyclass(unsendable)] -pub struct ContextIterator(VertexIterator<'static, Context>); +pub struct ContextIterator(VertexIterator<'static, Opaque>); impl ContextIterator { - fn new(inner: VertexIterator<'static, DataContext>>>) -> Self { - Self(Box::new(inner.map(Context))) + fn new>> + 'static>(inner: BaseContextIterator<'static, V>) -> Self { + Self(Box::new(inner.map(Opaque::new))) } } @@ -224,7 +249,7 @@ impl ContextIterator { slf } - fn __next__(mut slf: PyRefMut) -> Option { + fn __next__(mut slf: PyRefMut) -> Option { slf.0.next() } } @@ -256,13 +281,13 @@ impl Adapter<'static> for AdapterShim { }) } - fn resolve_property( + fn resolve_property + 'static>( &self, - contexts: BaseContextIterator<'static, Self::Vertex>, + contexts: BaseContextIterator<'static, V>, type_name: &Arc, property_name: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'static, V, FieldValue> { let contexts = ContextIterator::new(contexts); Python::with_gil(|py| { let py_iterable = self @@ -275,19 +300,28 @@ impl Adapter<'static> for AdapterShim { ) .unwrap(); - let iter = make_iterator(py, py_iterable).unwrap(); - Box::new(PythonResolvePropertyIterator::new(iter)) + let iter = PythonResolvePropertyIterator::new( + make_iterator(py, py_iterable).expect("failed to use py_iterable as an iterator"), + ); + + Box::new(iter.map(|(opaque, value)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_property()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, value) + })) }) } - fn resolve_neighbors( + fn resolve_neighbors + 'static>( &self, - contexts: BaseContextIterator<'static, Self::Vertex>, + contexts: BaseContextIterator<'static, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, VertexIterator<'static, Self::Vertex>> { + ) -> ContextOutcomeIterator<'static, V, VertexIterator<'static, Self::Vertex>> { let contexts = ContextIterator::new(contexts); Python::with_gil(|py| { let parameter_data: BTreeMap> = @@ -303,18 +337,26 @@ impl Adapter<'static> for AdapterShim { ) .unwrap(); - let iter = make_iterator(py, py_iterable).unwrap(); - Box::new(PythonResolveNeighborsIterator::new(iter)) + let iter = PythonResolveNeighborsIterator::new( + make_iterator(py, py_iterable).expect("failed to use py_iterable as an iterator"), + ); + Box::new(iter.map(|(opaque, neighbors)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_neighbors()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, neighbors) + })) }) } - fn resolve_coercion( + fn resolve_coercion + 'static>( &self, - contexts: BaseContextIterator<'static, Self::Vertex>, + contexts: BaseContextIterator<'static, V>, type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'static, V, bool> { let contexts = ContextIterator::new(contexts); Python::with_gil(|py| { let py_iterable = self @@ -327,8 +369,16 @@ impl Adapter<'static> for AdapterShim { ) .unwrap(); - let iter = make_iterator(py, py_iterable).unwrap(); - Box::new(PythonResolveCoercionIterator::new(iter)) + let iter = PythonResolveCoercionIterator::new( + make_iterator(py, py_iterable).expect("failed to use py_iterable as an iterator"), + ); + Box::new(iter.map(|(opaque, value)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_coercion()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, value) + })) }) } } @@ -373,14 +423,14 @@ impl PythonResolvePropertyIterator { } impl Iterator for PythonResolvePropertyIterator { - type Item = (DataContext>>, FieldValue); + type Item = (Opaque, FieldValue); fn next(&mut self) -> Option { Python::with_gil(|py| { match self.underlying.call_method(py, "__next__", (), None) { Ok(output) => { // value is a (context, property_value) tuple here - let context: Context = output + let context: Opaque = output .call_method(py, "__getitem__", (0i64,), None) .unwrap() .extract(py) @@ -393,7 +443,7 @@ impl Iterator for PythonResolvePropertyIterator { ) .unwrap(); - Some((context.0, value)) + Some((context, value)) } Err(e) => { if e.is_instance_of::(py) { @@ -420,14 +470,14 @@ impl PythonResolveNeighborsIterator { } impl Iterator for PythonResolveNeighborsIterator { - type Item = (DataContext>>, VertexIterator<'static, Arc>>); + type Item = (Opaque, VertexIterator<'static, Arc>>); fn next(&mut self) -> Option { Python::with_gil(|py| { match self.underlying.call_method(py, "__next__", (), None) { Ok(output) => { // value is a (context, neighbor_iterator) tuple here - let context: Context = output + let context: Opaque = output .call_method(py, "__getitem__", (0i64,), None) .unwrap() .extract(py) @@ -441,7 +491,7 @@ impl Iterator for PythonResolveNeighborsIterator { let neighbors: VertexIterator<'static, Arc>> = Box::new(PythonVertexIterator::new(neighbors_iter)); - Some((context.0, neighbors)) + Some((context, neighbors)) } Err(e) => { if e.is_instance_of::(py) { @@ -468,14 +518,14 @@ impl PythonResolveCoercionIterator { } impl Iterator for PythonResolveCoercionIterator { - type Item = (DataContext>>, bool); + type Item = (Opaque, bool); fn next(&mut self) -> Option { Python::with_gil(|py| { match self.underlying.call_method(py, "__next__", (), None) { Ok(output) => { // value is a (context, can_coerce) tuple here - let context: Context = output + let context: Opaque = output .call_method(py, "__getitem__", (0i64,), None) .unwrap() .extract(py) @@ -485,7 +535,7 @@ impl Iterator for PythonResolveCoercionIterator { .unwrap() .extract::(py) .unwrap(); - Some((context.0, can_coerce)) + Some((context, can_coerce)) } Err(e) => { if e.is_instance_of::(py) { diff --git a/trustfall/examples/feeds/adapter.rs b/trustfall/examples/feeds/adapter.rs index 4afcde0a..255c8a05 100644 --- a/trustfall/examples/feeds/adapter.rs +++ b/trustfall/examples/feeds/adapter.rs @@ -7,6 +7,7 @@ use trustfall::{ }, FieldValue, }; +use trustfall_core::interpreter::AsVertex; #[derive(Debug)] pub(crate) struct FeedAdapter<'a> { @@ -56,12 +57,12 @@ impl<'a> BasicAdapter<'a> for FeedAdapter<'a> { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, property_name: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match type_name { "Feed" => match property_name { "id" => property(contexts, field_property!(as_feed, id)), @@ -152,13 +153,13 @@ impl<'a> BasicAdapter<'a> for FeedAdapter<'a> { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, edge_name: &str, _parameters: &EdgeParameters, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match type_name { "Feed" => match edge_name { "title" => neighbors(contexts, iterable!(as_feed, title, Vertex::FeedText)), @@ -195,12 +196,12 @@ impl<'a> BasicAdapter<'a> for FeedAdapter<'a> { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - _contexts: ContextIterator<'a, Self::Vertex>, + _contexts: ContextIterator<'a, V>, type_name: &str, coerce_to_type: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { unimplemented!("{type_name} -> {coerce_to_type}") } } diff --git a/trustfall/examples/hackernews/adapter.rs b/trustfall/examples/hackernews/adapter.rs index 35b6def2..7253cc66 100644 --- a/trustfall/examples/hackernews/adapter.rs +++ b/trustfall/examples/hackernews/adapter.rs @@ -11,6 +11,7 @@ use trustfall::{ }, FieldValue, }; +use trustfall_core::interpreter::AsVertex; use crate::{get_schema, vertex::Vertex}; @@ -135,7 +136,7 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { let max = parameters.get("max").map(|v| v.as_u64().unwrap() as usize); self.top(max) } - "LatestStory" => { + "Latest" => { let max = parameters.get("max").map(|v| v.as_u64().unwrap() as usize); self.latest_stories(max) } @@ -147,12 +148,12 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, property_name: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match (type_name, property_name) { // properties on Item and its implementers (type_name, "id") if self.item_subtypes.contains(type_name) => { @@ -161,6 +162,20 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { (type_name, "unixTime") if self.item_subtypes.contains(type_name) => { resolve_property_with(contexts, item_property_resolver!(time)) } + (type_name, "url") if self.item_subtypes.contains(type_name) => { + resolve_property_with(contexts, |vertex: &Vertex| { + let id = match vertex { + Vertex::Story(x) => x.id, + Vertex::Job(x) => x.id, + Vertex::Comment(x) => x.id, + Vertex::Poll(x) => x.id, + Vertex::PollOption(x) => x.id, + Vertex::User(_) => unreachable!("found a User which is not an Item"), + }; + + format!("https://news.ycombinator.com/item?id={id}").into() + }) + } // properties on Job ("Job", "score") => resolve_property_with(contexts, field_property!(as_job, score)), @@ -177,7 +192,9 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { } ("Story", "score") => resolve_property_with(contexts, field_property!(as_story, score)), ("Story", "title") => resolve_property_with(contexts, field_property!(as_story, title)), - ("Story", "url") => resolve_property_with(contexts, field_property!(as_story, url)), + ("Story", "submittedUrl") => { + resolve_property_with(contexts, field_property!(as_story, url)) + } // properties on Comment ("Comment", "byUsername") => { @@ -205,13 +222,13 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, edge_name: &str, _parameters: &EdgeParameters, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match (type_name, edge_name) { ("Story", "byUser") => { let edge_resolver = |vertex: &Self::Vertex| -> VertexIterator<'a, Self::Vertex> { @@ -357,12 +374,12 @@ impl<'a> BasicAdapter<'a> for HackerNewsAdapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, _type_name: &str, coerce_to_type: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { resolve_coercion_using_schema(contexts, get_schema(), coerce_to_type) } } diff --git a/trustfall/examples/hackernews/example_queries/front_page_stories_with_links.ron b/trustfall/examples/hackernews/example_queries/front_page_stories_with_links.ron index dde66be9..5a01da8e 100644 --- a/trustfall/examples/hackernews/example_queries/front_page_stories_with_links.ron +++ b/trustfall/examples/hackernews/example_queries/front_page_stories_with_links.ron @@ -4,7 +4,7 @@ InputQuery ( FrontPage { ... on Story { title @output - url @filter(op: "is_not_null") @output + submittedUrl @filter(op: "is_not_null") @output score @output byUser { diff --git a/trustfall/examples/hackernews/example_queries/highly_rated_stories_by_specific_user.ron b/trustfall/examples/hackernews/example_queries/highly_rated_stories_by_specific_user.ron index 68265746..b9e92632 100644 --- a/trustfall/examples/hackernews/example_queries/highly_rated_stories_by_specific_user.ron +++ b/trustfall/examples/hackernews/example_queries/highly_rated_stories_by_specific_user.ron @@ -9,7 +9,7 @@ InputQuery ( score @output @filter(op: ">=", value: ["$min_score"]) title @output unixTime @output - url @output + submittedUrl @output text @output commentsCount @output } diff --git a/trustfall/examples/hackernews/example_queries/latest_links_by_high_karma_users.ron b/trustfall/examples/hackernews/example_queries/latest_links_by_high_karma_users.ron index 670e59ef..75ca137e 100644 --- a/trustfall/examples/hackernews/example_queries/latest_links_by_high_karma_users.ron +++ b/trustfall/examples/hackernews/example_queries/latest_links_by_high_karma_users.ron @@ -1,9 +1,9 @@ InputQuery ( query: r#" { - LatestStory(max: 100) { + Latest(max: 100) { title @output - url @filter(op: "is_not_null") @output + submittedUrl @filter(op: "is_not_null") @output score @output byUser { diff --git a/trustfall/examples/hackernews/example_queries/links_with_high_karma_commenters.ron b/trustfall/examples/hackernews/example_queries/links_with_high_karma_commenters.ron index 0507ed4f..83032fed 100644 --- a/trustfall/examples/hackernews/example_queries/links_with_high_karma_commenters.ron +++ b/trustfall/examples/hackernews/example_queries/links_with_high_karma_commenters.ron @@ -1,9 +1,9 @@ InputQuery ( query: r#" { - LatestStory(max: 100) { + Latest(max: 100) { title @output - url @filter(op: "is_not_null") @output + submittedUrl @filter(op: "is_not_null") @output score @output comment { diff --git a/trustfall/examples/hackernews/hackernews.graphql b/trustfall/examples/hackernews/hackernews.graphql index fe7d85de..db2a8b97 100644 --- a/trustfall/examples/hackernews/hackernews.graphql +++ b/trustfall/examples/hackernews/hackernews.graphql @@ -34,37 +34,39 @@ directive @transform( type RootSchemaQuery { FrontPage: [Item!]! Top(max: Int): [Item!]! - LatestStory(max: Int): [Story!]! + Latest(max: Int): [Story!]! User(name: String!): User } interface Item { id: Int! unixTime: Int! + url: String! } type Job implements Item { # properties from Item id: Int! unixTime: Int! + url: String! # own properties title: String! score: Int! - url: String! } type Story implements Item { # properties from Item id: Int! unixTime: Int! + url: String! # the URL of the HackerNews page for this story # own properties byUsername: String! score: Int! + submittedUrl: String # the URL this story is about, if any text: String title: String! - url: String commentsCount: Int! # edges @@ -76,6 +78,7 @@ type Comment implements Item { # properties from Item id: Int! unixTime: Int! + url: String! # own properties text: String! @@ -92,12 +95,14 @@ type Poll implements Item { # properties from Item id: Int! unixTime: Int! + url: String! } type PollOption implements Item { # properties from Item id: Int! unixTime: Int! + url: String! } type User { diff --git a/trustfall/examples/weather/adapter.rs b/trustfall/examples/weather/adapter.rs index ad1372d6..ab070d84 100644 --- a/trustfall/examples/weather/adapter.rs +++ b/trustfall/examples/weather/adapter.rs @@ -6,6 +6,7 @@ use trustfall::{ }, FieldValue, }; +use trustfall_core::interpreter::AsVertex; use crate::metar::{MetarCloudCover, MetarReport}; @@ -76,12 +77,12 @@ impl<'a> BasicAdapter<'a> for MetarAdapter<'a> { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, property_name: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match type_name { "MetarReport" => match property_name { "station_id" => { @@ -138,13 +139,13 @@ impl<'a> BasicAdapter<'a> for MetarAdapter<'a> { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, edge_name: &str, parameters: &EdgeParameters, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match (type_name, edge_name) { ("MetarReport", "cloud_cover") => { assert!(parameters.is_empty()); @@ -163,12 +164,12 @@ impl<'a> BasicAdapter<'a> for MetarAdapter<'a> { } #[allow(unused_variables)] - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &str, coerce_to_type: &str, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { unimplemented!("no types in our schema have subtypes") } } diff --git a/trustfall/src/lib.rs b/trustfall/src/lib.rs index de496f22..b6cfe74e 100644 --- a/trustfall/src/lib.rs +++ b/trustfall/src/lib.rs @@ -39,7 +39,7 @@ use std::{collections::BTreeMap, sync::Arc}; pub mod provider { pub use trustfall_core::interpreter::basic_adapter::BasicAdapter; pub use trustfall_core::interpreter::{ - Adapter, CandidateValue, ContextIterator, ContextOutcomeIterator, DataContext, + Adapter, AsVertex, CandidateValue, ContextIterator, ContextOutcomeIterator, DataContext, DynamicallyResolvedValue, EdgeInfo, QueryInfo, Range, RequiredProperty, ResolveEdgeInfo, ResolveInfo, Typename, VertexInfo, VertexIterator, }; diff --git a/trustfall/tests/helper_macros.rs b/trustfall/tests/helper_macros.rs index 0cfa68eb..e4f3acb6 100644 --- a/trustfall/tests/helper_macros.rs +++ b/trustfall/tests/helper_macros.rs @@ -28,13 +28,12 @@ impl trustfall::provider::BasicAdapter<'static> for Adapter { ) -> trustfall::provider::VertexIterator<'static, Self::Vertex> { Box::new(std::iter::once(V::Variant(Value { name: String::from("Berit") }))) } - fn resolve_property( + fn resolve_property + 'static>( &self, - contexts: trustfall::provider::ContextIterator<'static, Self::Vertex>, + contexts: trustfall::provider::ContextIterator<'static, V>, type_name: &str, property_name: &str, - ) -> trustfall::provider::ContextOutcomeIterator<'static, Self::Vertex, trustfall::FieldValue> - { + ) -> trustfall::provider::ContextOutcomeIterator<'static, V, trustfall::FieldValue> { match (type_name, property_name) { ("Vertex", "name") => trustfall::provider::resolve_property_with( contexts, @@ -48,26 +47,26 @@ impl trustfall::provider::BasicAdapter<'static> for Adapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'static>( &self, - _contexts: trustfall::provider::ContextIterator<'static, Self::Vertex>, + _contexts: trustfall::provider::ContextIterator<'static, V>, _type_name: &str, _edge_name: &str, _parameters: &trustfall::provider::EdgeParameters, ) -> trustfall::provider::ContextOutcomeIterator< 'static, - Self::Vertex, + V, trustfall::provider::VertexIterator<'static, Self::Vertex>, > { todo!("schema should not contain neighbors: Berit likes it that way") } - fn resolve_coercion( + fn resolve_coercion + 'static>( &self, - _contexts: trustfall::provider::ContextIterator<'static, Self::Vertex>, + _contexts: trustfall::provider::ContextIterator<'static, V>, _type_name: &str, _coerce_to_type: &str, - ) -> trustfall::provider::ContextOutcomeIterator<'static, Self::Vertex, bool> { + ) -> trustfall::provider::ContextOutcomeIterator<'static, V, bool> { todo!("there is only ever one Berit") } } diff --git a/trustfall_core/fuzz/fuzz_targets/adapter_batching/mod.rs b/trustfall_core/fuzz/fuzz_targets/adapter_batching/mod.rs index 19518bc5..a3849129 100644 --- a/trustfall_core/fuzz/fuzz_targets/adapter_batching/mod.rs +++ b/trustfall_core/fuzz/fuzz_targets/adapter_batching/mod.rs @@ -16,7 +16,7 @@ use walkdir::WalkDir; extern crate trustfall_core; use trustfall_core::{ - interpreter::{execution::interpret_ir, Adapter}, + interpreter::{execution::interpret_ir, Adapter, AsVertex}, ir::{FieldValue, IndexedQuery}, }; @@ -99,13 +99,13 @@ impl<'a, AdapterT: Adapter<'a> + 'a> Adapter<'a> for VariableBatchingAdapter<'a, Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: trustfall_core::interpreter::ContextIterator<'a, Self::Vertex>, + contexts: trustfall_core::interpreter::ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &trustfall_core::interpreter::ResolveInfo, - ) -> trustfall_core::interpreter::ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> trustfall_core::interpreter::ContextOutcomeIterator<'a, V, FieldValue> { let mut cursor_ref = self.cursor.borrow_mut(); let sequence = cursor_ref.read_u64::().unwrap_or(0); drop(cursor_ref); @@ -119,16 +119,16 @@ impl<'a, AdapterT: Adapter<'a> + 'a> Adapter<'a> for VariableBatchingAdapter<'a, Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: trustfall_core::interpreter::ContextIterator<'a, Self::Vertex>, + contexts: trustfall_core::interpreter::ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &trustfall_core::ir::EdgeParameters, resolve_info: &trustfall_core::interpreter::ResolveEdgeInfo, ) -> trustfall_core::interpreter::ContextOutcomeIterator< 'a, - Self::Vertex, + V, trustfall_core::interpreter::VertexIterator<'a, Self::Vertex>, > { let mut cursor_ref = self.cursor.borrow_mut(); @@ -145,13 +145,13 @@ impl<'a, AdapterT: Adapter<'a> + 'a> Adapter<'a> for VariableBatchingAdapter<'a, Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: trustfall_core::interpreter::ContextIterator<'a, Self::Vertex>, + contexts: trustfall_core::interpreter::ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &trustfall_core::interpreter::ResolveInfo, - ) -> trustfall_core::interpreter::ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> trustfall_core::interpreter::ContextOutcomeIterator<'a, V, bool> { let mut cursor_ref = self.cursor.borrow_mut(); let sequence = cursor_ref.read_u64::().unwrap_or(0); drop(cursor_ref); diff --git a/trustfall_core/fuzz/fuzz_targets/adapter_batching/numbers_adapter.rs b/trustfall_core/fuzz/fuzz_targets/adapter_batching/numbers_adapter.rs index 888d5407..8da41af9 100644 --- a/trustfall_core/fuzz/fuzz_targets/adapter_batching/numbers_adapter.rs +++ b/trustfall_core/fuzz/fuzz_targets/adapter_batching/numbers_adapter.rs @@ -6,8 +6,8 @@ use maplit::btreeset; use trustfall_core::{ interpreter::{ helpers::{resolve_coercion_with, resolve_neighbors_with, resolve_property_with}, - Adapter, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, Typename, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, + Typename, VertexIterator, }, ir::{EdgeParameters, FieldValue}, }; @@ -214,13 +214,13 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { return resolve_property_with(contexts, |vertex| Number::typename(vertex).into()); } @@ -241,14 +241,14 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { let mut primes = btreeset![2, 3]; let parameters = parameters.clone(); match (type_name.as_ref(), edge_name.as_ref()) { @@ -353,13 +353,13 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { match (type_name.as_ref(), coerce_to_type.as_ref()) { ("Number", "Prime") => { resolve_coercion_with(contexts, |vertex| matches!(vertex, NumbersVertex::Prime(..))) diff --git a/trustfall_core/src/filesystem_interpreter.rs b/trustfall_core/src/filesystem_interpreter.rs index 2059276d..eb7b3153 100644 --- a/trustfall_core/src/filesystem_interpreter.rs +++ b/trustfall_core/src/filesystem_interpreter.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; +use crate::interpreter::AsVertex; use crate::{ interpreter::{ Adapter, ContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, @@ -160,31 +161,30 @@ pub type ContextAndValue = (DataContext, FieldValue); type IndividualEdgeResolver<'a> = fn(Rc, &FilesystemVertex) -> VertexIterator<'a, FilesystemVertex>; -type ContextAndIterableOfEdges<'a> = - (DataContext, VertexIterator<'a, FilesystemVertex>); +type ContextAndIterableOfEdges<'a, V> = (DataContext, VertexIterator<'a, FilesystemVertex>); -struct EdgeResolverIterator<'a> { +struct EdgeResolverIterator<'a, V: AsVertex> { origin: Rc, - contexts: VertexIterator<'a, DataContext>, + contexts: VertexIterator<'a, DataContext>, edge_resolver: IndividualEdgeResolver<'a>, } -impl<'a> EdgeResolverIterator<'a> { +impl<'a, V: AsVertex> EdgeResolverIterator<'a, V> { pub fn new( origin: Rc, - contexts: VertexIterator<'a, DataContext>, + contexts: VertexIterator<'a, DataContext>, edge_resolver: IndividualEdgeResolver<'a>, ) -> Self { Self { origin, contexts, edge_resolver } } } -impl<'a> Iterator for EdgeResolverIterator<'a> { - type Item = (DataContext, VertexIterator<'a, FilesystemVertex>); +impl<'a, V: AsVertex> Iterator for EdgeResolverIterator<'a, V> { + type Item = (DataContext, VertexIterator<'a, FilesystemVertex>); - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option> { if let Some(context) = self.contexts.next() { - if let Some(vertex) = context.active_vertex() { + if let Some(vertex) = context.active_vertex::() { let neighbors = (self.edge_resolver)(self.origin.clone(), vertex); Some((context, neighbors)) } else { @@ -254,80 +254,99 @@ impl<'a> Adapter<'a> for FilesystemInterpreter { Box::new(OriginIterator::new(vertex)) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match type_name.as_ref() { - "Directory" => match property_name.as_ref() { - "name" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(FilesystemVertex::Directory(ref x)) => { - let value = FieldValue::String(x.name.clone().into()); - (context, value) - } - _ => unreachable!(), - })), - "path" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(FilesystemVertex::Directory(ref x)) => { - let value = FieldValue::String(x.path.clone().into()); - (context, value) - } - _ => unreachable!(), - })), - "__typename" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(_) => (context, "Directory".into()), - })), - _ => todo!(), - }, - "File" => match property_name.as_ref() { - "name" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(FilesystemVertex::File(ref x)) => { - let value = FieldValue::String(x.name.clone().into()); - (context, value) - } - _ => unreachable!(), - })), - "path" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(FilesystemVertex::File(ref x)) => { - let value = FieldValue::String(x.path.clone().into()); - (context, value) - } - _ => unreachable!(), - })), - "extension" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(FilesystemVertex::File(ref x)) => { - let value = x.extension.clone().map(Into::into).unwrap_or(FieldValue::Null); - (context, value) - } - _ => unreachable!(), - })), - "__typename" => Box::new(contexts.map(|context| match context.active_vertex() { - None => (context, FieldValue::Null), - Some(_) => (context, "File".into()), - })), - _ => todo!(), - }, + "Directory" => { + match property_name.as_ref() { + "name" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(FilesystemVertex::Directory(ref x)) => { + let value = FieldValue::String(x.name.clone().into()); + (context, value) + } + _ => unreachable!(), + } + })), + "path" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(FilesystemVertex::Directory(ref x)) => { + let value = FieldValue::String(x.path.clone().into()); + (context, value) + } + _ => unreachable!(), + } + })), + "__typename" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(_) => (context, "Directory".into()), + } + })), + _ => todo!(), + } + } + "File" => { + match property_name.as_ref() { + "name" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(FilesystemVertex::File(ref x)) => { + let value = FieldValue::String(x.name.clone().into()); + (context, value) + } + _ => unreachable!(), + } + })), + "path" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(FilesystemVertex::File(ref x)) => { + let value = FieldValue::String(x.path.clone().into()); + (context, value) + } + _ => unreachable!(), + } + })), + "extension" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(FilesystemVertex::File(ref x)) => { + let value = + x.extension.clone().map(Into::into).unwrap_or(FieldValue::Null); + (context, value) + } + _ => unreachable!(), + } + })), + "__typename" => Box::new(contexts.map(|context| { + match context.active_vertex::() { + None => (context, FieldValue::Null), + Some(_) => (context, "File".into()), + } + })), + _ => todo!(), + } + } _ => todo!(), } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match (type_name.as_ref(), edge_name.as_ref()) { ("Directory", "out_Directory_ContainsFile") => { let iterator = EdgeResolverIterator::new( @@ -349,13 +368,13 @@ impl<'a> Adapter<'a> for FilesystemInterpreter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { todo!() } } diff --git a/trustfall_core/src/interpreter/basic_adapter.rs b/trustfall_core/src/interpreter/basic_adapter.rs index d41de4ec..b834b77e 100644 --- a/trustfall_core/src/interpreter/basic_adapter.rs +++ b/trustfall_core/src/interpreter/basic_adapter.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use crate::ir::{EdgeParameters, FieldValue}; use super::{ - helpers::resolve_property_with, Adapter, ContextIterator, ContextOutcomeIterator, + helpers::resolve_property_with, Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, Typename, VertexIterator, }; @@ -72,12 +72,12 @@ pub trait BasicAdapter<'vertex> { /// - When a context's active vertex is `None`, its property value is `FieldValue::Null`. /// /// [`DataContext`]: super::DataContext - fn resolve_property( + fn resolve_property + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &str, property_name: &str, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, FieldValue>; + ) -> ContextOutcomeIterator<'vertex, V, FieldValue>; /// Resolve the neighboring vertices across an edge, for each query context in an iterator. /// @@ -103,13 +103,13 @@ pub trait BasicAdapter<'vertex> { /// - Produce contexts in the same order as the input `contexts` iterator produced them. /// - Each neighboring vertex is of the type specified for that edge in the schema. /// - When a context's active vertex is None, it has an empty neighbors iterator. - fn resolve_neighbors( + fn resolve_neighbors + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &str, edge_name: &str, parameters: &EdgeParameters, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, VertexIterator<'vertex, Self::Vertex>>; + ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>>; /// Attempt to coerce vertices to a subtype, over an iterator of query contexts. /// @@ -147,12 +147,12 @@ pub trait BasicAdapter<'vertex> { /// - Produce contexts in the same order as the input `contexts` iterator produced them. /// - Each neighboring vertex is of the type specified for that edge in the schema. /// - When a context's active vertex is `None`, its coercion outcome is `false`. - fn resolve_coercion( + fn resolve_coercion + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &str, coerce_to_type: &str, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, bool>; + ) -> ContextOutcomeIterator<'vertex, V, bool>; /// Resolve the `__typename` special property over an iterator of query contexts. /// @@ -201,11 +201,11 @@ pub trait BasicAdapter<'vertex> { /// /// [`DataContext`]: super::DataContext /// [`Schema`]: crate::schema::Schema - fn resolve_typename( + fn resolve_typename + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, _type_name: &str, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'vertex, V, FieldValue> { resolve_property_with(contexts, |vertex| vertex.typename().into()) } } @@ -225,13 +225,13 @@ where ::resolve_starting_vertices(self, edge_name.as_ref(), parameters) } - fn resolve_property( + fn resolve_property + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &std::sync::Arc, property_name: &std::sync::Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'vertex, V, FieldValue> { if property_name.as_ref() == "__typename" { return self.resolve_typename(contexts, type_name); } @@ -244,14 +244,14 @@ where ) } - fn resolve_neighbors( + fn resolve_neighbors + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &std::sync::Arc, edge_name: &std::sync::Arc, parameters: &EdgeParameters, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, VertexIterator<'vertex, Self::Vertex>> { + ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>> { ::resolve_neighbors( self, contexts, @@ -261,13 +261,13 @@ where ) } - fn resolve_coercion( + fn resolve_coercion + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &std::sync::Arc, coerce_to_type: &std::sync::Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'vertex, V, bool> { ::resolve_coercion( self, contexts, diff --git a/trustfall_core/src/interpreter/execution.rs b/trustfall_core/src/interpreter/execution.rs index 64dee053..73a87c87 100644 --- a/trustfall_core/src/interpreter/execution.rs +++ b/trustfall_core/src/interpreter/execution.rs @@ -14,7 +14,7 @@ use crate::{ }; use super::{ - error::QueryArgumentsError, filtering::apply_filter, Adapter, ContextIterator, + error::QueryArgumentsError, filtering::apply_filter, Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, DataContext, InterpretedQuery, ResolveEdgeInfo, ResolveInfo, TaggedValue, ValueOrVec, VertexIterator, }; @@ -766,13 +766,17 @@ fn apply_fold_specific_filter<'query, AdapterT: Adapter<'query>>( ) } -pub(super) fn compute_context_field_with_separate_value<'query, AdapterT: Adapter<'query>>( +pub(super) fn compute_context_field_with_separate_value< + 'query, + AdapterT: Adapter<'query>, + V: AsVertex + 'query, +>( adapter: &AdapterT, carrier: &mut QueryCarrier, component: &IRQueryComponent, context_field: &ContextField, - iterator: Box> + 'query>, -) -> Box, TaggedValue)> + 'query> { + iterator: Box> + 'query>, +) -> Box, TaggedValue)> + 'query> { let vertex_id = context_field.vertex_id; if let Some(vertex) = component.vertices.get(&vertex_id) { @@ -1344,8 +1348,8 @@ mod tests { use crate::{ interpreter::{ - execution::interpret_ir, Adapter, ContextIterator, ContextOutcomeIterator, - ResolveEdgeInfo, ResolveInfo, VertexIterator, + execution::interpret_ir, Adapter, AsVertex, ContextIterator, + ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue, IndexedQuery}, numbers_interpreter::NumbersAdapter, @@ -1434,13 +1438,13 @@ mod tests { Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { let mut batch_sequences_ref = self.batch_sequences.borrow_mut(); let sequence = batch_sequences_ref.pop_front().unwrap_or(0); drop(batch_sequences_ref); @@ -1454,15 +1458,14 @@ mod tests { Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> - { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { let mut batch_sequences_ref = self.batch_sequences.borrow_mut(); let sequence = batch_sequences_ref.pop_front().unwrap_or(0); drop(batch_sequences_ref); @@ -1477,13 +1480,13 @@ mod tests { Box::new(VariableChunkIterator::new(inner, sequence)) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { let mut batch_sequences_ref = self.batch_sequences.borrow_mut(); let sequence = batch_sequences_ref.pop_front().unwrap_or(0); drop(batch_sequences_ref); diff --git a/trustfall_core/src/interpreter/helpers/mod.rs b/trustfall_core/src/interpreter/helpers/mod.rs index 7375a65e..01f211f7 100644 --- a/trustfall_core/src/interpreter/helpers/mod.rs +++ b/trustfall_core/src/interpreter/helpers/mod.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeSet, fmt::Debug}; use crate::{ir::FieldValue, schema::Schema}; -use super::{ContextIterator, ContextOutcomeIterator, Typename, VertexIterator}; +use super::{AsVertex, ContextIterator, ContextOutcomeIterator, Typename, VertexIterator}; mod correctness; @@ -20,11 +20,15 @@ pub use correctness::check_adapter_invariants; /// [`accessor_property!`](crate::accessor_property) macros. /// /// [`BasicAdapter::resolve_property`]: super::basic_adapter::BasicAdapter::resolve_property -pub fn resolve_property_with<'vertex, Vertex: Debug + Clone + 'vertex>( - contexts: ContextIterator<'vertex, Vertex>, +pub fn resolve_property_with< + 'vertex, + Vertex: Debug + Clone + 'vertex, + V: AsVertex + 'vertex, +>( + contexts: ContextIterator<'vertex, V>, mut resolver: impl FnMut(&Vertex) -> FieldValue + 'vertex, -) -> ContextOutcomeIterator<'vertex, Vertex, FieldValue> { - Box::new(contexts.map(move |ctx| match ctx.active_vertex.as_ref() { +) -> ContextOutcomeIterator<'vertex, V, FieldValue> { + Box::new(contexts.map(move |ctx| match ctx.active_vertex::() { None => (ctx, FieldValue::Null), Some(vertex) => { let value = resolver(vertex); @@ -39,12 +43,16 @@ pub fn resolve_property_with<'vertex, Vertex: Debug + Clone + 'vertex>( /// in the input context iterator, one at a time. /// /// [`BasicAdapter::resolve_neighbors`]: super::basic_adapter::BasicAdapter::resolve_neighbors -pub fn resolve_neighbors_with<'vertex, Vertex: Debug + Clone + 'vertex>( - contexts: ContextIterator<'vertex, Vertex>, +pub fn resolve_neighbors_with< + 'vertex, + Vertex: Debug + Clone + 'vertex, + V: AsVertex + 'vertex, +>( + contexts: ContextIterator<'vertex, V>, mut resolver: impl FnMut(&Vertex) -> VertexIterator<'vertex, Vertex> + 'vertex, -) -> ContextOutcomeIterator<'vertex, Vertex, VertexIterator<'vertex, Vertex>> { +) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Vertex>> { Box::new(contexts.map(move |ctx| { - match ctx.active_vertex.as_ref() { + match ctx.active_vertex::() { None => { // rustc needs a bit of help with the type inference here, // due to the Box conversion. @@ -65,11 +73,15 @@ pub fn resolve_neighbors_with<'vertex, Vertex: Debug + Clone + 'vertex>( /// in the input context iterator, one at a time. /// /// [`BasicAdapter::resolve_coercion`]: super::basic_adapter::BasicAdapter::resolve_coercion -pub fn resolve_coercion_with<'vertex, Vertex: Debug + Clone + 'vertex>( - contexts: ContextIterator<'vertex, Vertex>, +pub fn resolve_coercion_with< + 'vertex, + Vertex: Debug + Clone + 'vertex, + V: AsVertex + 'vertex, +>( + contexts: ContextIterator<'vertex, V>, mut resolver: impl FnMut(&Vertex) -> bool + 'vertex, -) -> ContextOutcomeIterator<'vertex, Vertex, bool> { - Box::new(contexts.map(move |ctx| match ctx.active_vertex.as_ref() { +) -> ContextOutcomeIterator<'vertex, V, bool> { + Box::new(contexts.map(move |ctx| match ctx.active_vertex::() { None => (ctx, false), Some(vertex) => { let can_coerce = resolver(vertex); @@ -85,11 +97,15 @@ pub fn resolve_coercion_with<'vertex, Vertex: Debug + Clone + 'vertex>( /// and checks if it's equal or a subtype of the coercion target type. /// /// [`BasicAdapter::resolve_coercion`]: super::basic_adapter::BasicAdapter::resolve_coercion -pub fn resolve_coercion_using_schema<'vertex, Vertex: Debug + Clone + Typename + 'vertex>( - contexts: ContextIterator<'vertex, Vertex>, +pub fn resolve_coercion_using_schema< + 'vertex, + Vertex: Debug + Clone + Typename + 'vertex, + V: AsVertex + 'vertex, +>( + contexts: ContextIterator<'vertex, V>, schema: &'vertex Schema, coerce_to_type: &str, -) -> ContextOutcomeIterator<'vertex, Vertex, bool> { +) -> ContextOutcomeIterator<'vertex, V, bool> { // If the vertex's typename is one of these types, // then the coercion's result is `true`. let subtypes: BTreeSet<_> = schema @@ -97,7 +113,7 @@ pub fn resolve_coercion_using_schema<'vertex, Vertex: Debug + Clone + Typename + .unwrap_or_else(|| panic!("type {coerce_to_type} is not part of this schema")) .collect(); - Box::new(contexts.map(move |ctx| match ctx.active_vertex.as_ref() { + Box::new(contexts.map(move |ctx| match ctx.active_vertex::() { None => (ctx, false), Some(vertex) => { let typename = vertex.typename(); @@ -384,11 +400,11 @@ macro_rules! accessor_property { /// a faster path. /// /// [`Adapter::resolve_property`]: super::Adapter::resolve_property -pub fn resolve_typename<'a, Vertex: Typename + Debug + Clone + 'a>( - contexts: ContextIterator<'a, Vertex>, +pub fn resolve_typename<'a, Vertex: Typename + Debug + Clone + 'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, schema: &Schema, type_name: &str, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { // `type_name` is the statically-known type. The vertices are definitely *at least* that type, // but could also be one of its subtypes. If there are no subtypes, they *must* be that type. let mut subtypes_iter = match schema.subtypes(type_name) { @@ -400,7 +416,7 @@ pub fn resolve_typename<'a, Vertex: Typename + Debug + Clone + 'a>( // Is there a subtype that isn't the starting type itself? if subtypes_iter.any(|name| name != type_name) { // Subtypes exist, we have to check each vertex separately. - resolve_property_with(contexts, |vertex| vertex.typename().into()) + resolve_property_with::(contexts, |vertex| vertex.typename().into()) } else { // No other subtypes exist. // All vertices here must be of exactly `type_name` type. diff --git a/trustfall_core/src/interpreter/helpers/tests.rs b/trustfall_core/src/interpreter/helpers/tests.rs index 8a7fb448..71a53c90 100644 --- a/trustfall_core/src/interpreter/helpers/tests.rs +++ b/trustfall_core/src/interpreter/helpers/tests.rs @@ -67,8 +67,8 @@ mod correctness { use crate::{ interpreter::{ - Adapter, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, + ResolveInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue}, numbers_interpreter::NumbersAdapter, @@ -93,13 +93,13 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if type_name.as_ref() == "Named" && property_name.as_ref() == "__typename" { panic!("oops! we forgot to implement __typename on Named"); } @@ -107,14 +107,14 @@ mod correctness { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -125,13 +125,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -161,24 +161,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { if type_name.as_ref() == "Neither" && edge_name.as_ref() == "predecessor" { panic!("oops! we forgot to implement predecessor edge on type Neither"); @@ -193,13 +193,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -229,24 +229,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -257,13 +257,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { if type_name.as_ref() == "Named" && coerce_to_type.as_ref() == "Number" { panic!("oops! we forgot to implement coercion from Named to Number"); } @@ -286,8 +286,8 @@ mod correctness { use crate::{ interpreter::{ - Adapter, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, + ResolveInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue}, numbers_interpreter::NumbersAdapter, @@ -313,13 +313,13 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - mut contexts: ContextIterator<'a, Self::Vertex>, + mut contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if type_name.as_ref() == "Named" && property_name.as_ref() == "__typename" { // This is a context we consume from the input // but don't return in the output iterator. @@ -329,14 +329,14 @@ mod correctness { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -347,13 +347,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -386,24 +386,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - mut contexts: ContextIterator<'a, Self::Vertex>, + mut contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { if type_name.as_ref() == "Neither" && edge_name.as_ref() == "predecessor" { // This is a context we consume from the input @@ -420,13 +420,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -457,24 +457,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -485,13 +485,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - mut contexts: ContextIterator<'a, Self::Vertex>, + mut contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { if type_name.as_ref() == "Named" && coerce_to_type.as_ref() == "Number" { // This is a context we consume from the input // but don't return in the output iterator. @@ -516,8 +516,8 @@ mod correctness { use crate::{ interpreter::{ - Adapter, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, + ResolveInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue}, numbers_interpreter::NumbersAdapter, @@ -545,13 +545,13 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if type_name.as_ref() == "Named" && property_name.as_ref() == "__typename" { let mut all_contexts: Vec<_> = contexts.collect(); let popped = all_contexts.swap_remove(3); @@ -572,14 +572,14 @@ mod correctness { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -590,13 +590,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -629,24 +629,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { if type_name.as_ref() == "Neither" && edge_name.as_ref() == "predecessor" { let mut all_contexts: Vec<_> = contexts.collect(); @@ -670,13 +670,13 @@ mod correctness { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) } } @@ -709,24 +709,24 @@ mod correctness { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { self.inner.resolve_neighbors( contexts, @@ -737,13 +737,13 @@ mod correctness { ) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { if type_name.as_ref() == "Named" && coerce_to_type.as_ref() == "Number" { let mut all_contexts: Vec<_> = contexts.collect(); let popped = all_contexts.swap_remove(3); diff --git a/trustfall_core/src/interpreter/hints/dynamic.rs b/trustfall_core/src/interpreter/hints/dynamic.rs index c85280c8..ad3f2b7d 100644 --- a/trustfall_core/src/interpreter/hints/dynamic.rs +++ b/trustfall_core/src/interpreter/hints/dynamic.rs @@ -7,7 +7,7 @@ use crate::{ compute_fold_specific_field_with_separate_value, QueryCarrier, }, hints::Range, - Adapter, ContextIterator, ContextOutcomeIterator, InterpretedQuery, TaggedValue, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, InterpretedQuery, TaggedValue, VertexIterator, }, ir::{ @@ -59,7 +59,7 @@ use super::CandidateValue; /// # use trustfall_core::{ /// # ir::{EdgeParameters, FieldValue}, /// # interpreter::{ -/// # Adapter, CandidateValue, ContextIterator, ContextOutcomeIterator, +/// # Adapter, AsVertex, CandidateValue, ContextIterator, ContextOutcomeIterator, /// # ResolveEdgeInfo, ResolveInfo, VertexInfo, VertexIterator, /// # }, /// # }; @@ -78,59 +78,59 @@ use super::CandidateValue; /// # todo!() /// # } /// # -/// # fn resolve_property( +/// # fn resolve_property + 'a>( /// # &self, -/// # contexts: ContextIterator<'a, Self::Vertex>, +/// # contexts: ContextIterator<'a, V>, /// # type_name: &Arc, /// # property_name: &Arc, /// # resolve_info: &ResolveInfo, -/// # ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { +/// # ) -> ContextOutcomeIterator<'a, V, FieldValue> { /// # todo!() /// # } /// # -/// # fn resolve_neighbors( +/// # fn resolve_neighbors + 'a>( /// # &self, -/// # contexts: ContextIterator<'a, Self::Vertex>, +/// # contexts: ContextIterator<'a, V>, /// # type_name: &Arc, /// # edge_name: &Arc, /// # parameters: &EdgeParameters, /// # resolve_info: &ResolveEdgeInfo, -/// # ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { +/// # ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { /// # todo!() /// # } /// # -/// # fn resolve_coercion( +/// # fn resolve_coercion + 'a>( /// # &self, -/// # contexts: ContextIterator<'a, Self::Vertex>, +/// # contexts: ContextIterator<'a, V>, /// # type_name: &Arc, /// # coerce_to_type: &Arc, /// # resolve_info: &ResolveInfo, -/// # ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { +/// # ) -> ContextOutcomeIterator<'a, V, bool> { /// # todo!() /// # } /// # } /// # -/// # fn resolve_recipient_from_candidate_value<'a>( -/// # vertex: &Vertex, +/// # fn resolve_recipient_from_candidate_value<'a, V>( +/// # vertex: &V, /// # candidate: CandidateValue /// # ) -> VertexIterator<'a, Vertex> { /// # todo!() /// # } /// # -/// # fn resolve_recipient_otherwise<'a>( -/// # contexts: ContextIterator<'a, Vertex>, -/// # ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +/// # fn resolve_recipient_otherwise<'a, V>( +/// # contexts: ContextIterator<'a, V>, +/// # ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { /// # todo!() /// # } /// # /// # impl EmailAdapter { /// // Inside our adapter implementation: /// // we use this method to resolve `recipient` edges. -/// fn resolve_recipient_edge<'a>( +/// fn resolve_recipient_edge<'a, V: AsVertex + 'a>( /// &self, -/// contexts: ContextIterator<'a, Vertex>, +/// contexts: ContextIterator<'a, V>, /// resolve_info: &ResolveEdgeInfo, -/// ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +/// ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { /// if let Some(dynamic_value) = resolve_info.destination().dynamically_required_property("address") { /// // The query is looking for a specific recipient's address, /// // so let's look it up directly. @@ -195,11 +195,11 @@ impl<'a> DynamicallyResolvedValue<'a> { } #[allow(dead_code)] // false-positive: dead in the bin target, not dead in the lib - pub fn resolve<'vertex, AdapterT: Adapter<'vertex>>( + pub fn resolve<'vertex, AdapterT: Adapter<'vertex>, V: AsVertex + 'vertex>( self, adapter: &AdapterT, - contexts: ContextIterator<'vertex, AdapterT::Vertex>, - ) -> ContextOutcomeIterator<'vertex, AdapterT::Vertex, CandidateValue> { + contexts: ContextIterator<'vertex, V>, + ) -> ContextOutcomeIterator<'vertex, V, CandidateValue> { match &self.field { FieldRef::ContextField(context_field) => { if context_field.vertex_id < self.resolve_on_component.root { @@ -230,17 +230,17 @@ impl<'a> DynamicallyResolvedValue<'a> { } #[allow(dead_code)] // false-positive: dead in the bin target, not dead in the lib - pub fn resolve_with<'vertex, AdapterT: Adapter<'vertex>>( + pub fn resolve_with< + 'vertex, + AdapterT: Adapter<'vertex>, + V: AsVertex + 'vertex, + >( self, adapter: &AdapterT, - contexts: ContextIterator<'vertex, AdapterT::Vertex>, - mut neighbor_resolver: impl FnMut( - &AdapterT::Vertex, - CandidateValue, - ) -> VertexIterator<'vertex, AdapterT::Vertex> + contexts: ContextIterator<'vertex, V>, + mut neighbor_resolver: impl FnMut(&V, CandidateValue) -> VertexIterator<'vertex, AdapterT::Vertex> + 'vertex, - ) -> ContextOutcomeIterator<'vertex, AdapterT::Vertex, VertexIterator<'vertex, AdapterT::Vertex>> - { + ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, AdapterT::Vertex>> { Box::new(self.resolve(adapter, contexts).map(move |(ctx, candidate)| { let neighbors = match ctx.active_vertex.as_ref() { Some(vertex) => neighbor_resolver(vertex, candidate), @@ -250,12 +250,16 @@ impl<'a> DynamicallyResolvedValue<'a> { })) } - fn compute_candidate_from_tagged_value<'vertex, AdapterT: Adapter<'vertex>>( + fn compute_candidate_from_tagged_value< + 'vertex, + AdapterT: Adapter<'vertex>, + V: AsVertex + 'vertex, + >( self, context_field: &'a ContextField, adapter: &AdapterT, - contexts: ContextIterator<'vertex, AdapterT::Vertex>, - ) -> ContextOutcomeIterator<'vertex, AdapterT::Vertex, CandidateValue> { + contexts: ContextIterator<'vertex, V>, + ) -> ContextOutcomeIterator<'vertex, V, CandidateValue> { let mut carrier = QueryCarrier { query: Some(self.query) }; let iterator = compute_context_field_with_separate_value( adapter, diff --git a/trustfall_core/src/interpreter/hints/tests/mod.rs b/trustfall_core/src/interpreter/hints/tests/mod.rs index becbebe2..0fb2cc34 100644 --- a/trustfall_core/src/interpreter/hints/tests/mod.rs +++ b/trustfall_core/src/interpreter/hints/tests/mod.rs @@ -3,8 +3,8 @@ use std::{cell::RefCell, collections::BTreeMap, num::NonZeroUsize, path::PathBuf use super::{ResolveEdgeInfo, ResolveInfo}; use crate::{ interpreter::{ - execution::interpret_ir, Adapter, ContextIterator, ContextOutcomeIterator, VertexInfo, - VertexIterator, + execution::interpret_ir, Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, + VertexInfo, VertexIterator, }, ir::{Eid, FieldValue, Recursive, Vid}, numbers_interpreter::{NumbersAdapter, NumbersVertex}, @@ -87,13 +87,13 @@ impl<'a> Adapter<'a> for TestAdapter { self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &super::ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { let mut map_ref = self.on_property_resolver.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.current_vid) { x.call(resolve_info); @@ -102,14 +102,14 @@ impl<'a> Adapter<'a> for TestAdapter { self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &crate::ir::EdgeParameters, resolve_info: &super::ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { let mut map_ref = self.on_edge_resolver.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.eid()) { x.call(resolve_info); @@ -118,13 +118,13 @@ impl<'a> Adapter<'a> for TestAdapter { self.inner.resolve_neighbors(contexts, type_name, edge_name, parameters, resolve_info) } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &super::ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { let mut map_ref = self.on_type_coercion.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.current_vid) { x.call(resolve_info); @@ -1245,9 +1245,45 @@ mod dynamic_property_values { use super::*; - type CtxIter = ContextIterator<'static, >::Vertex>; - type ResolveInfoFn = Box CtxIter>; - type ResolveEdgeInfoFn = Box CtxIter>; + trait ResolveInfoFn { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V>; + } + + impl ResolveInfoFn for () { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + _info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + ctxs + } + } + + trait ResolveEdgeInfoFn { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V>; + } + + impl ResolveEdgeInfoFn for () { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + _info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + ctxs + } + } #[derive(Default)] struct TrackCalls { @@ -1261,34 +1297,39 @@ mod dynamic_property_values { } } - impl TrackCalls { - fn call(&mut self, adapter: &NumbersAdapter, ctxs: CtxIter, info: &ResolveInfo) -> CtxIter { + impl TrackCalls { + fn resolve_call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { self.calls += 1; - (self.underlying)(adapter, ctxs, info) + self.underlying.call(adapter, ctxs, info) } } - impl TrackCalls { - fn call( + impl TrackCalls { + fn resolve_edge_call + 'static>( &mut self, adapter: &NumbersAdapter, - ctxs: CtxIter, + ctxs: ContextIterator<'static, V>, info: &ResolveEdgeInfo, - ) -> CtxIter { + ) -> ContextIterator<'static, V> { self.calls += 1; - (self.underlying)(adapter, ctxs, info) + self.underlying.call(adapter, ctxs, info) } } - struct DynamicTestAdapter { - on_starting_vertices: RefCell>>, - on_property_resolver: RefCell>>, - on_edge_resolver: RefCell>>, - on_type_coercion: RefCell>>, + struct DynamicTestAdapter { + on_starting_vertices: RefCell>>, + on_property_resolver: RefCell>>, + on_edge_resolver: RefCell>>, + on_type_coercion: RefCell>>, inner: NumbersAdapter, } - impl DynamicTestAdapter { + impl DynamicTestAdapter { fn new() -> Self { Self { inner: NumbersAdapter::new(), @@ -1300,72 +1341,77 @@ mod dynamic_property_values { } } - impl Default for DynamicTestAdapter { + impl Default for DynamicTestAdapter { fn default() -> Self { Self::new() } } - impl Adapter<'static> for DynamicTestAdapter { + impl + Adapter<'static> for DynamicTestAdapter + { type Vertex = >::Vertex; fn resolve_starting_vertices( &self, edge_name: &Arc, parameters: &crate::ir::EdgeParameters, - resolve_info: &super::ResolveInfo, + resolve_info: &ResolveInfo, ) -> VertexIterator<'static, Self::Vertex> { let mut map_ref = self.on_starting_vertices.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.current_vid) { // the starting vertices call doesn't have an iterator - let _ = x.call(&self.inner, Box::new(std::iter::empty()), resolve_info); + let _ = x.resolve_call::( + &self.inner, + Box::new(std::iter::empty()), + resolve_info, + ); } drop(map_ref); self.inner.resolve_starting_vertices(edge_name, parameters, resolve_info) } - fn resolve_property( + fn resolve_property + 'static>( &self, - mut contexts: ContextIterator<'static, Self::Vertex>, + mut contexts: ContextIterator<'static, V>, type_name: &Arc, property_name: &Arc, - resolve_info: &super::ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, FieldValue> { + resolve_info: &ResolveInfo, + ) -> ContextOutcomeIterator<'static, V, FieldValue> { let mut map_ref = self.on_property_resolver.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.current_vid) { - contexts = x.call(&self.inner, contexts, resolve_info); + contexts = x.resolve_call(&self.inner, contexts, resolve_info); } drop(map_ref); self.inner.resolve_property(contexts, type_name, property_name, resolve_info) } - fn resolve_neighbors( + fn resolve_neighbors + 'static>( &self, - mut contexts: ContextIterator<'static, Self::Vertex>, + mut contexts: ContextIterator<'static, V>, type_name: &Arc, edge_name: &Arc, parameters: &crate::ir::EdgeParameters, - resolve_info: &super::ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, VertexIterator<'static, Self::Vertex>> - { + resolve_info: &ResolveEdgeInfo, + ) -> ContextOutcomeIterator<'static, V, VertexIterator<'static, Self::Vertex>> { let mut map_ref = self.on_edge_resolver.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.eid()) { - contexts = x.call(&self.inner, contexts, resolve_info); + contexts = x.resolve_edge_call(&self.inner, contexts, resolve_info); } drop(map_ref); self.inner.resolve_neighbors(contexts, type_name, edge_name, parameters, resolve_info) } - fn resolve_coercion( + fn resolve_coercion + 'static>( &self, - mut contexts: ContextIterator<'static, Self::Vertex>, + mut contexts: ContextIterator<'static, V>, type_name: &Arc, coerce_to_type: &Arc, - resolve_info: &super::ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, bool> { + resolve_info: &ResolveInfo, + ) -> ContextOutcomeIterator<'static, V, bool> { let mut map_ref = self.on_type_coercion.borrow_mut(); if let Some(x) = map_ref.get_mut(&resolve_info.current_vid) { - contexts = x.call(&self.inner, contexts, resolve_info); + contexts = x.resolve_call(&self.inner, contexts, resolve_info); } drop(map_ref); self.inner.resolve_coercion(contexts, type_name, coerce_to_type, resolve_info) @@ -1378,38 +1424,53 @@ mod dynamic_property_values { fn static_and_dynamic_filter() { let input_name = "static_and_dynamic_filter"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); + struct StartingVertices; - // This property isn't known or needed at all. - assert_eq!(None, info.dynamically_required_property("name")); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &super::ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - let edge = info.first_edge("successor").expect("no 'successor' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 yet, so this property - // isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("value")); + // This property isn't known or needed at all. + assert_eq!(None, info.dynamically_required_property("name")); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - assert_eq!(eid(1), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(2), info.destination_vid()); + let edge = info.first_edge("successor").expect("no 'successor' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 yet, so this property + // isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("value")); - let destination = info.destination(); + ctxs + } + } + + struct EdgeResolver; - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(3)), - CandidateValue::Multiple(vec![FieldValue::Int64(3), FieldValue::Int64(4)]), - ]; - let value_candidate = destination.dynamically_required_property("value"); - Box::new(value_candidate + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(eid(1), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(2), info.destination_vid()); + + let destination = info.destination(); + + let expected_values = [ + CandidateValue::Single(FieldValue::Int64(3)), + CandidateValue::Multiple(vec![FieldValue::Int64(3), FieldValue::Int64(4)]), + ]; + let value_candidate = destination.dynamically_required_property("value"); + Box::new( + value_candidate .expect("no dynamic candidate for 'value' property") .resolve(adapter, ctxs) .zip_longest(expected_values) @@ -1420,11 +1481,23 @@ mod dynamic_property_values { } else { panic!("unexpected iterator outcome: {data:?}") } - })) - })), - }.into(), - ..Default::default() - }; + }), + ) + } + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1438,39 +1511,72 @@ mod dynamic_property_values { fn recurse_then_filter_on_tag_depth_one() { let input_name = "recurse_then_filter_on_tag_depth_one"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); + struct StartingVertices; - let edge = info.first_edge("successor").expect("no 'successor' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 yet, so this property - // isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("value")); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - assert_eq!(eid(1), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(2), info.destination_vid()); + let edge = info.first_edge("successor").expect("no 'successor' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 yet, so this property + // isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("value")); - let destination = info.destination(); + ctxs + } + } + + struct EdgeResolver; + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(eid(1), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(2), info.destination_vid()); - let expected_values = [ - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(0)), true)), - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(1)), true)), - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(2)), true)), - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(3)), true)), - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(4)), true)), - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(5)), true)), - ]; - let value_candidate = destination.dynamically_required_property("value"); - Box::new(value_candidate + let destination = info.destination(); + + let expected_values = [ + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(0)), + true, + )), + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(1)), + true, + )), + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(2)), + true, + )), + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(3)), + true, + )), + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(4)), + true, + )), + CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(5)), + true, + )), + ]; + let value_candidate = destination.dynamically_required_property("value"); + Box::new( + value_candidate .expect("no dynamic candidate for 'value' property") .resolve(adapter, ctxs) .zip_longest(expected_values) @@ -1481,11 +1587,23 @@ mod dynamic_property_values { } else { panic!("unexpected iterator outcome: {data:?}") } - })) - })), - }.into(), - ..Default::default() - }; + }), + ) + } + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1499,38 +1617,61 @@ mod dynamic_property_values { fn recurse_then_filter_on_tag_depth_two() { let input_name = "recurse_then_filter_on_tag_depth_two"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); + struct StartingVertices; - let edge = info.first_edge("successor").expect("no 'successor' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 yet, so this property - // isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("value")); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - ctxs - })), + let edge = info.first_edge("successor").expect("no 'successor' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 yet, so this property + // isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("value")); + + ctxs } - .into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(eid(1), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(2), info.destination_vid()); + } - let destination = info.destination(); + struct EdgeResolver; - let value_candidate = destination.dynamically_required_property("value"); - assert_eq!(None, value_candidate); - ctxs - })), + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(eid(1), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(2), info.destination_vid()); + + let destination = info.destination(); + + let value_candidate = destination.dynamically_required_property("value"); + assert_eq!(None, value_candidate); + ctxs } - .into(), - ..Default::default() - }; + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1541,34 +1682,48 @@ mod dynamic_property_values { fn filter_in_fold_using_external_tag() { let input_name = "filter_in_fold_using_external_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); + struct StartingVertices; - let edge = info.first_edge("multiple").expect("no 'multiple' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 yet, so this property - // isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("name")); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - assert_eq!(eid(1), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(2), info.destination_vid()); + let edge = info.first_edge("multiple").expect("no 'multiple' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 yet, so this property + // isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("name")); - let destination = info.destination(); + ctxs + } + } + + struct EdgeResolver; + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(eid(1), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(2), info.destination_vid()); - let expected_values = [ - CandidateValue::Range(Range::with_end(Bound::Excluded("two".into()), true)), - ]; - let candidate = destination.dynamically_required_property("name"); - Box::new(candidate + let destination = info.destination(); + + let expected_values = + [CandidateValue::Range(Range::with_end(Bound::Excluded("two".into()), true))]; + let candidate = destination.dynamically_required_property("name"); + Box::new( + candidate .expect("no dynamic candidate for 'value' property") .resolve(adapter, ctxs) .zip_longest(expected_values) @@ -1579,11 +1734,23 @@ mod dynamic_property_values { } else { panic!("unexpected iterator outcome: {data:?}") } - })) - })), - }.into(), - ..Default::default() - }; + }), + ) + } + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1594,67 +1761,99 @@ mod dynamic_property_values { fn filter_in_nested_fold_using_external_tag() { let input_name = "filter_in_nested_fold_using_external_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); - - let edge = info.first_edge("multiple").expect("no 'multiple' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 yet, so this property - // isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("name")); + struct StartingVertices; - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(eid(1), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(2), info.destination_vid()); - - let edge = info - .destination() - .first_edge("multiple") - .expect("no 'multiple' edge"); - let destination = edge.destination(); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - // For the purposes of *this* edge, the subsequent fold's values aren't yet - // dynamically known: no matter their value, they can't affect the vertices - // that this edge resolves to. - assert_eq!(None, destination.dynamically_required_property("name")); + let edge = info.first_edge("multiple").expect("no 'multiple' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 yet, so this property + // isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("name")); - ctxs - })), - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - assert_eq!(eid(2), info.eid()); - assert_eq!(vid(2), info.origin_vid()); - assert_eq!(vid(3), info.destination_vid()); + ctxs + } + } - let destination = info.destination(); + struct EdgeResolver(Eid); + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + match self.0 .0.into() { + 1 => { + assert_eq!(eid(1), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(2), info.destination_vid()); + + let edge = + info.destination().first_edge("multiple").expect("no 'multiple' edge"); + let destination = edge.destination(); + + // For the purposes of *this* edge, the subsequent fold's values aren't yet + // dynamically known: no matter their value, they can't affect the vertices + // that this edge resolves to. + assert_eq!(None, destination.dynamically_required_property("name")); + + ctxs + } + 2 => { + assert_eq!(eid(2), info.eid()); + assert_eq!(vid(2), info.origin_vid()); + assert_eq!(vid(3), info.destination_vid()); + + let destination = info.destination(); + + let expected_values = [CandidateValue::Range(Range::with_end( + Bound::Excluded("two".into()), + true, + ))]; + let candidate = destination.dynamically_required_property("name"); + Box::new( + candidate + .expect("no dynamic candidate for 'name' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + _ => unreachable!("{:?}", self.0), + } + } + } - let expected_values = [ - CandidateValue::Range(Range::with_end(Bound::Excluded("two".into()), true)), - ]; - let candidate = destination.dynamically_required_property("name"); - Box::new(candidate - .expect("no dynamic candidate for 'name' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - }.into(), - ..Default::default() - }; + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver(eid(1))), + eid(2) => TrackCalls::new_underlying(EdgeResolver(eid(2))), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1666,34 +1865,47 @@ mod dynamic_property_values { fn fold_count_tag_explicitly_named() { let input_name = "fold_count_tag_explicitly_named"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(1), info.vid()); + struct StartingVertices; - let edge = info.first_edge("predecessor").expect("no 'predecessor' edge"); - let destination = edge.destination(); - // We haven't resolved Vid 1 nor Vid 2 yet, - // so this property isn't dynamically known yet. - assert_eq!(None, destination.dynamically_required_property("value")); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert!(info.coerced_to_type().is_none()); + assert_eq!(vid(1), info.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - assert_eq!(eid(2), info.eid()); - assert_eq!(vid(1), info.origin_vid()); - assert_eq!(vid(3), info.destination_vid()); + let edge = info.first_edge("predecessor").expect("no 'predecessor' edge"); + let destination = edge.destination(); + // We haven't resolved Vid 1 nor Vid 2 yet, + // so this property isn't dynamically known yet. + assert_eq!(None, destination.dynamically_required_property("value")); - let destination = info.destination(); + ctxs + } + } + + struct EdgeResolver; - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(1)), - ]; - let candidate = destination.dynamically_required_property("value"); - Box::new(candidate + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(eid(2), info.eid()); + assert_eq!(vid(1), info.origin_vid()); + assert_eq!(vid(3), info.destination_vid()); + + let destination = info.destination(); + + let expected_values = [CandidateValue::Single(FieldValue::Int64(1))]; + let candidate = destination.dynamically_required_property("value"); + Box::new( + candidate .expect("no dynamic candidate for 'value' property") .resolve(adapter, ctxs) .zip_longest(expected_values) @@ -1704,11 +1916,23 @@ mod dynamic_property_values { } else { panic!("unexpected iterator outcome: {data:?}") } - })) - })), - }.into(), - ..Default::default() - }; + }), + ) + } + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(2) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1719,51 +1943,76 @@ mod dynamic_property_values { fn optional_with_nested_filter_with_tag_semantics() { let input_name = "optional_with_nested_filter_with_tag_semantics"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(vid(1), info.vid()); - assert!(info.coerced_to_type().is_none()); + struct StartingVertices; - let edge_info = info.first_edge("predecessor").expect("no 'predecessor' edge info"); - let neighbor = edge_info.destination(); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(vid(1), info.vid()); + assert!(info.coerced_to_type().is_none()); - assert_eq!(vid(2), neighbor.vid()); + let edge_info = info.first_edge("predecessor").expect("no 'predecessor' edge info"); + let neighbor = edge_info.destination(); - // This value actually *isn't* dynamically known, because - // for the purposes of the encompassing `@optional` it's important - // to know whether *any* edges exist, even if none will match the filter. - // - // Including the filter's value here would be a footgun. - // In the absence of a way for the resolver to indicate - // "no matching edges existed, but some other edges were present" - // the safest thing to do is to return `None` here. - assert_eq!(None, neighbor.dynamically_required_property("value")); + assert_eq!(vid(2), neighbor.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(2), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + // This value actually *isn't* dynamically known, because + // for the purposes of the encompassing `@optional` it's important + // to know whether *any* edges exist, even if none will match the filter. + // + // Including the filter's value here would be a footgun. + // In the absence of a way for the resolver to indicate + // "no matching edges existed, but some other edges were present" + // the safest thing to do is to return `None` here. + assert_eq!(None, neighbor.dynamically_required_property("value")); - // This value actually *isn't* dynamically known, because - // for the purposes of the encompassing `@optional` it's important - // to know whether *any* edges exist, even if none will match the filter. - // - // Including the filter's value here would be a footgun. - // In the absence of a way for the resolver to indicate - // "no matching edges existed, but some other edges were present" - // the safest thing to do is to return `None` here. - assert_eq!(None, destination.dynamically_required_property("value")); + ctxs + } + } - ctxs - })), - }.into(), - ..Default::default() - }; + struct EdgeResolver; + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + let destination = info.destination(); + assert_eq!(vid(2), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + // This value actually *isn't* dynamically known, because + // for the purposes of the encompassing `@optional` it's important + // to know whether *any* edges exist, even if none will match the filter. + // + // Including the filter's value here would be a footgun. + // In the absence of a way for the resolver to indicate + // "no matching edges existed, but some other edges were present" + // the safest thing to do is to return `None` here. + assert_eq!(None, destination.dynamically_required_property("value")); + + ctxs + } + } + + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1774,82 +2023,118 @@ mod dynamic_property_values { fn optional_with_nested_edge_with_filter_and_tag() { let input_name = "optional_with_nested_edge_with_filter_and_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(vid(1), info.vid()); - assert!(info.coerced_to_type().is_none()); - - let edge_info = info.first_edge("predecessor").expect("no 'predecessor' edge info"); - let neighbor = edge_info.destination(); - assert_eq!(vid(2), neighbor.vid()); + struct StartingVertices; - let next_edge_info = neighbor.first_edge("successor").expect("no 'successor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(vid(1), info.vid()); + assert!(info.coerced_to_type().is_none()); - // This value actually *isn't* dynamically known, because - // for the purposes of the encompassing `@optional` it's important - // to know whether *any* edges exist, even if none will match the filter. - // - // Including the filter's value here would be a footgun. - // In the absence of a way for the resolver to indicate - // "no matching edges existed, but some other edges were present" - // the safest thing to do is to return `None` here. - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + let edge_info = info.first_edge("predecessor").expect("no 'predecessor' edge info"); + let neighbor = edge_info.destination(); + assert_eq!(vid(2), neighbor.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(2), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + let next_edge_info = + neighbor.first_edge("successor").expect("no 'successor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); - let next_edge_info = destination.first_edge("successor").expect("no 'successor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + // This value actually *isn't* dynamically known, because + // for the purposes of the encompassing `@optional` it's important + // to know whether *any* edges exist, even if none will match the filter. + // + // Including the filter's value here would be a footgun. + // In the absence of a way for the resolver to indicate + // "no matching edges existed, but some other edges were present" + // the safest thing to do is to return `None` here. + assert_eq!(None, next_neighbor.dynamically_required_property("value")); - // This value actually *isn't* dynamically known, because - // for the purposes of the encompassing `@optional` it's important - // to know whether *any* edges exist, even if none will match the filter. - // - // Including the filter's value here would be a footgun. - // In the absence of a way for the resolver to indicate - // "no matching edges existed, but some other edges were present" - // the safest thing to do is to return `None` here. - assert_eq!(None, next_neighbor.statically_required_property("value")); + ctxs + } + } - ctxs - })), - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(3), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + struct EdgeResolver(Eid); + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + match self.0 .0.into() { + 1 => { + let destination = info.destination(); + assert_eq!(vid(2), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + let next_edge_info = + destination.first_edge("successor").expect("no 'successor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); + + // This value actually *isn't* dynamically known, because + // for the purposes of the encompassing `@optional` it's important + // to know whether *any* edges exist, even if none will match the filter. + // + // Including the filter's value here would be a footgun. + // In the absence of a way for the resolver to indicate + // "no matching edges existed, but some other edges were present" + // the safest thing to do is to return `None` here. + assert_eq!(None, next_neighbor.statically_required_property("value")); + + ctxs + } + 2 => { + let destination = info.destination(); + assert_eq!(vid(3), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + // Here the value *is* dynamically known, since the `@optional` + // has already been resolved in a prior step. + let expected_values = [CandidateValue::Range(Range::with_start( + Bound::Excluded(FieldValue::Int64(1)), + true, + ))]; + let candidate = destination.dynamically_required_property("value"); + Box::new( + candidate + .expect("no dynamic candidate for 'value' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + _ => unreachable!("{:?}", self.0), + } + } + } - // Here the value *is* dynamically known, since the `@optional` - // has already been resolved in a prior step. - let expected_values = [ - CandidateValue::Range(Range::with_start(Bound::Excluded(FieldValue::Int64(1)), true)), - ]; - let candidate = destination.dynamically_required_property("value"); - Box::new(candidate - .expect("no dynamic candidate for 'value' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - }.into(), - ..Default::default() - }; + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver(eid(1))), + eid(2) => TrackCalls::new_underlying(EdgeResolver(eid(2))), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -1861,76 +2146,110 @@ mod dynamic_property_values { fn fold_with_nested_filter_and_tag() { let input_name = "fold_with_nested_filter_and_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(vid(1), info.vid()); - assert!(info.coerced_to_type().is_none()); - - let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); - let neighbor = edge_info.destination(); - assert_eq!(vid(2), neighbor.vid()); + struct StartingVertices; - let next_edge_info = neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(vid(1), info.vid()); + assert!(info.coerced_to_type().is_none()); - // This value actually *isn't* dynamically known here, because - // for the purposes of the edge being resolved, no values for that property - // can possibly cause the *currently-resolved edge* to be discarded. - // - // Including the filter's value here would be a footgun. - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); + let neighbor = edge_info.destination(); + assert_eq!(vid(2), neighbor.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(2), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + let next_edge_info = + neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); - let next_edge_info = destination.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + // This value actually *isn't* dynamically known here, because + // for the purposes of the edge being resolved, no values for that property + // can possibly cause the *currently-resolved edge* to be discarded. + // + // Including the filter's value here would be a footgun. + assert_eq!(None, next_neighbor.dynamically_required_property("value")); - // This value actually *isn't* dynamically known here, because - // for the purposes of the edge being resolved, no values for that property - // can possibly cause the *currently-resolved edge* to be discarded. - // - // Including the filter's value here would be a footgun. - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + ctxs + } + } - ctxs - })), - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(3), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + struct EdgeResolver(Eid); + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + match self.0 .0.into() { + 1 => { + let destination = info.destination(); + assert_eq!(vid(2), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + let next_edge_info = destination + .first_edge("predecessor") + .expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); + + // This value actually *isn't* dynamically known here, because + // for the purposes of the edge being resolved, no values for that property + // can possibly cause the *currently-resolved edge* to be discarded. + // + // Including the filter's value here would be a footgun. + assert_eq!(None, next_neighbor.dynamically_required_property("value")); + + ctxs + } + 2 => { + let destination = info.destination(); + assert_eq!(vid(3), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + // Here the value *is* dynamically known, since the property value can + // affect which vertices this edge is resolved to. + let expected_values = [CandidateValue::Single(FieldValue::Int64(1))]; + let candidate = destination.dynamically_required_property("value"); + Box::new( + candidate + .expect("no dynamic candidate for 'value' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + _ => unreachable!("{:?}", self.0), + } + } + } - // Here the value *is* dynamically known, since the property value can - // affect which vertices this edge is resolved to. - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(1)), - ]; - let candidate = destination.dynamically_required_property("value"); - Box::new(candidate - .expect("no dynamic candidate for 'value' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - }.into(), - ..Default::default() - }; + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver(eid(1))), + eid(2) => TrackCalls::new_underlying(EdgeResolver(eid(2))), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); assert_eq!(adapter.on_edge_resolver.borrow()[&eid(1)].calls, 1); @@ -1941,84 +2260,118 @@ mod dynamic_property_values { fn fold_with_count_filter_and_nested_filter_with_tag() { let input_name = "fold_with_count_filter_and_nested_filter_with_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(vid(1), info.vid()); - assert!(info.coerced_to_type().is_none()); + struct StartingVertices; - let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); - let neighbor = edge_info.destination(); - assert_eq!(vid(2), neighbor.vid()); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(vid(1), info.vid()); + assert!(info.coerced_to_type().is_none()); - let next_edge_info = neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); + let neighbor = edge_info.destination(); + assert_eq!(vid(2), neighbor.vid()); - // This value is not yet dynamically known: Vid 1 hasn't been resolved yet, - // and the dynamic candidate value depends on Vid 1. - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + let next_edge_info = + neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(2), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + // This value is not yet dynamically known: Vid 1 hasn't been resolved yet, + // and the dynamic candidate value depends on Vid 1. + assert_eq!(None, next_neighbor.dynamically_required_property("value")); - let next_edge_info = destination.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + ctxs + } + } - // This value *is* dynamically known here: the "fold-count-filter" around it - // ensures that at least one such value must exist, or else vertices - // from the currently-resolved edge will be discarded. - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(1)), - ]; - let candidate = next_neighbor.dynamically_required_property("value"); - Box::new(candidate - .expect("no dynamic candidate for 'value' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(3), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + struct EdgeResolver(Eid); + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + match self.0 .0.into() { + 1 => { + let destination = info.destination(); + assert_eq!(vid(2), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + let next_edge_info = destination + .first_edge("predecessor") + .expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); + + // This value *is* dynamically known here: the "fold-count-filter" around it + // ensures that at least one such value must exist, or else vertices + // from the currently-resolved edge will be discarded. + let expected_values = [CandidateValue::Single(FieldValue::Int64(1))]; + let candidate = next_neighbor.dynamically_required_property("value"); + Box::new( + candidate + .expect("no dynamic candidate for 'value' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + 2 => { + let destination = info.destination(); + assert_eq!(vid(3), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + // Here the value is also dynamically known, since the property is local + // to the edge being resolved. + let expected_values = [CandidateValue::Single(FieldValue::Int64(1))]; + let candidate = destination.dynamically_required_property("value"); + Box::new( + candidate + .expect("no dynamic candidate for 'value' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + _ => unreachable!("{:?}", self.0), + } + } + } - // Here the value is also dynamically known, since the property is local - // to the edge being resolved. - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(1)), - ]; - let candidate = destination.dynamically_required_property("value"); - Box::new(candidate - .expect("no dynamic candidate for 'value' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - }.into(), - ..Default::default() - }; + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver(eid(1))), + eid(2) => TrackCalls::new_underlying(EdgeResolver(eid(2))), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); @@ -2036,87 +2389,121 @@ mod dynamic_property_values { fn fold_with_both_count_and_nested_filter_dependent_on_tag() { let input_name = "fold_with_both_count_and_nested_filter_dependent_on_tag"; - let adapter = DynamicTestAdapter { - on_starting_vertices: btreemap! { - vid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - assert_eq!(vid(1), info.vid()); - assert!(info.coerced_to_type().is_none()); + struct StartingVertices; - let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); - let neighbor = edge_info.destination(); - assert_eq!(vid(2), neighbor.vid()); + impl ResolveInfoFn for StartingVertices { + fn call + 'static>( + &mut self, + _adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveInfo, + ) -> ContextIterator<'static, V> { + assert_eq!(vid(1), info.vid()); + assert!(info.coerced_to_type().is_none()); - let next_edge_info = neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); + let edge_info = info.first_edge("successor").expect("no 'successor' edge info"); + let neighbor = edge_info.destination(); + assert_eq!(vid(2), neighbor.vid()); - // This value is not yet dynamically known: Vid 1 hasn't been resolved yet, - // and the dynamic candidate value depends on Vid 1. - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + let next_edge_info = + neighbor.first_edge("predecessor").expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); - ctxs - })), - }.into(), - on_edge_resolver: btreemap! { - eid(1) => TrackCalls::::new_underlying(Box::new(|_, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(2), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + // This value is not yet dynamically known: Vid 1 hasn't been resolved yet, + // and the dynamic candidate value depends on Vid 1. + assert_eq!(None, next_neighbor.dynamically_required_property("value")); - let next_edge_info = destination.first_edge("predecessor").expect("no 'predecessor' edge info"); - let next_neighbor = next_edge_info.destination(); - assert_eq!(vid(3), next_neighbor.vid()); - - // This is where the current implementation is more conservative than - // strictly necessary: - // - // If the implementation were able to read the tagged value used in - // the fold-count-filter, it could determine that the fold is required - // to have at least one element and subsequently determine the - // dynamically-known value of `CandidateValue::Single(FieldValue::Int64(1))` - // for the `value` property below. - // - // If some instances of the tagged value are insufficient to require - // at least one folded element, those contexts would get - // `CandidateValue::All` for the dynamically-known value read below. - // - // However, the current implementation is not smart enough to do this, - // and instead conservatively returns `None` instead. - // This test pins down that behavior. - // - // TODO: consider making the hints analysis smarter here, - // as described above - assert_eq!(None, next_neighbor.dynamically_required_property("value")); + ctxs + } + } - ctxs - })), - eid(2) => TrackCalls::::new_underlying(Box::new(|adapter, ctxs, info| { - let destination = info.destination(); - assert_eq!(vid(3), destination.vid()); - assert!(destination.coerced_to_type().is_none()); + struct EdgeResolver(Eid); + + impl ResolveEdgeInfoFn for EdgeResolver { + fn call + 'static>( + &mut self, + adapter: &NumbersAdapter, + ctxs: ContextIterator<'static, V>, + info: &ResolveEdgeInfo, + ) -> ContextIterator<'static, V> { + match self.0 .0.into() { + 1 => { + let destination = info.destination(); + assert_eq!(vid(2), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + let next_edge_info = destination + .first_edge("predecessor") + .expect("no 'predecessor' edge info"); + let next_neighbor = next_edge_info.destination(); + assert_eq!(vid(3), next_neighbor.vid()); + + // This is where the current implementation is more conservative than + // strictly necessary: + // + // If the implementation were able to read the tagged value used in + // the fold-count-filter, it could determine that the fold is required + // to have at least one element and subsequently determine the + // dynamically-known value of `CandidateValue::Single(FieldValue::Int64(1))` + // for the `value` property below. + // + // If some instances of the tagged value are insufficient to require + // at least one folded element, those contexts would get + // `CandidateValue::All` for the dynamically-known value read below. + // + // However, the current implementation is not smart enough to do this, + // and instead conservatively returns `None` instead. + // This test pins down that behavior. + // + // TODO: consider making the hints analysis smarter here, + // as described above + assert_eq!(None, next_neighbor.dynamically_required_property("value")); + + ctxs + } + 2 => { + let destination = info.destination(); + assert_eq!(vid(3), destination.vid()); + assert!(destination.coerced_to_type().is_none()); + + // Here the value is also dynamically known, since the property is local + // to the edge being resolved. + let expected_values = [CandidateValue::Single(FieldValue::Int64(1))]; + let candidate = destination.dynamically_required_property("value"); + Box::new( + candidate + .expect("no dynamic candidate for 'value' property") + .resolve(adapter, ctxs) + .zip_longest(expected_values) + .map(move |data| { + if let EitherOrBoth::Both((ctx, value), expected_value) = data { + assert_eq!(expected_value, value); + ctx + } else { + panic!("unexpected iterator outcome: {data:?}") + } + }), + ) + } + _ => unreachable!("{:?}", self.0), + } + } + } - // Here the value is also dynamically known, since the property is local - // to the edge being resolved. - let expected_values = [ - CandidateValue::Single(FieldValue::Int64(1)), - ]; - let candidate = destination.dynamically_required_property("value"); - Box::new(candidate - .expect("no dynamic candidate for 'value' property") - .resolve(adapter, ctxs) - .zip_longest(expected_values) - .map(move |data| { - if let EitherOrBoth::Both((ctx, value), expected_value) = data { - assert_eq!(expected_value, value); - ctx - } else { - panic!("unexpected iterator outcome: {data:?}") - } - })) - })), - }.into(), - ..Default::default() - }; + let adapter: DynamicTestAdapter = + DynamicTestAdapter { + on_starting_vertices: btreemap! { + vid(1) => TrackCalls::new_underlying(StartingVertices), + } + .into(), + on_edge_resolver: btreemap! { + eid(1) => TrackCalls::new_underlying(EdgeResolver(eid(1))), + eid(2) => TrackCalls::new_underlying(EdgeResolver(eid(2))), + } + .into(), + ..Default::default() + }; let adapter = run_query(adapter, input_name); assert_eq!(adapter.on_starting_vertices.borrow()[&vid(1)].calls, 1); diff --git a/trustfall_core/src/interpreter/mod.rs b/trustfall_core/src/interpreter/mod.rs index 3d970aad..99697eda 100644 --- a/trustfall_core/src/interpreter/mod.rs +++ b/trustfall_core/src/interpreter/mod.rs @@ -92,8 +92,80 @@ impl DataContext { /// - [`Adapter::resolve_neighbors`] must produce an empty iterator of neighbors /// such as `Box::new(std::iter::empty())` for that context. /// - [`Adapter::resolve_coercion`] must produce a `false` coercion outcome for that context. - pub fn active_vertex(&self) -> Option<&Vertex> { - self.active_vertex.as_ref() + pub fn active_vertex(&self) -> Option<&V> + where + Vertex: AsVertex, + { + self.active_vertex.as_ref().and_then(AsVertex::as_vertex) + } + + /// Converts `DataContext` to `DataContext` by mapping each `Vertex` to `Other`. + /// + /// If you are implementing an [`Adapter`] for a data source, + /// you almost certainly *should not* be using this function. + /// You're probably looking for [`DataContext::active_vertex()`] instead. + pub fn map(self, mapper: &mut impl FnMut(Vertex) -> Other) -> DataContext { + DataContext { + active_vertex: self.active_vertex.map(&mut *mapper), + vertices: self.vertices.into_iter().map(|(k, v)| (k, v.map(&mut *mapper))).collect(), + values: self.values, + suspended_vertices: self + .suspended_vertices + .into_iter() + .map(|v| v.map(&mut *mapper)) + .collect(), + folded_contexts: self + .folded_contexts + .into_iter() + .map(|(k, ctxs)| { + (k, ctxs.map(|v| v.into_iter().map(|ctx| ctx.map(&mut *mapper)).collect())) + }) + .collect(), + folded_values: self.folded_values, + piggyback: self + .piggyback + .map(|v| v.into_iter().map(|ctx| ctx.map(&mut *mapper)).collect()), + imported_tags: self.imported_tags, + } + } + + /// Map each `Vertex` to `Option`, thus converting `Self` to `DataContext`. + /// + /// This is the [`DataContext`] equivalent of [`Option::and_then`][option], which is also + /// referred to as "flat-map" in some languages. + /// + /// If you are implementing an [`Adapter`] for a data source, + /// you almost certainly *should not* be using this function. + /// You're probably looking for [`DataContext::active_vertex()`] instead. + /// + /// [option]: https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then + pub fn flat_map(self, mapper: &mut impl FnMut(Vertex) -> Option) -> DataContext { + DataContext { + active_vertex: self.active_vertex.and_then(&mut *mapper), + vertices: self + .vertices + .into_iter() + .map(|(k, v)| (k, v.and_then(&mut *mapper))) + .collect::>>(), + values: self.values, + suspended_vertices: self + .suspended_vertices + .into_iter() + .map(|v| v.and_then(&mut *mapper)) + .collect(), + folded_contexts: self + .folded_contexts + .into_iter() + .map(|(k, ctxs)| { + (k, ctxs.map(|v| v.into_iter().map(|ctx| ctx.flat_map(&mut *mapper)).collect())) + }) + .collect(), + folded_values: self.folded_values, + piggyback: self + .piggyback + .map(|v| v.into_iter().map(|ctx| ctx.flat_map(&mut *mapper)).collect()), + imported_tags: self.imported_tags, + } } } @@ -387,37 +459,64 @@ fn validate_argument_type( /// Trustfall data providers implement this trait to enable querying their data sets. /// -/// The most straightforward way to implement this trait is by implementing -/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) instead, which is a simpler version -/// of this trait and is faster to implement. +/// The most straightforward way to implement this trait is to use +/// the [`trustfall_stubgen` code-generator tool][stubgen] tool to auto-generate stubs +/// customized to match your dataset's schema, then fill in the blanks denoted by `todo!()`. /// -/// Most often, it's best to first implement [`BasicAdapter`](self::basic_adapter::BasicAdapter) and -/// only convert to a direct implementation of this trait if your use case absolutely demands it: +/// If you prefer to implement the trait without code generation, consider implementing +/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) instead. That's a simpler version +/// of this trait and can be faster to implement without a significant loss of functionality: +/// - Both traits support the same set of queries. Under the hood, +/// [`BasicAdapter`](self::basic_adapter::BasicAdapter) itself implements [`Adapter`]. /// - If you need optimizations like batching or caching, you can implement them within /// [`BasicAdapter`](self::basic_adapter::BasicAdapter) as well. /// - If you need more advanced optimizations such as predicate pushdown, or need to access /// Trustfall's static analysis capabilities, implement this trait directly instead. +/// +/// [stubgen]: https://docs.rs/trustfall_stubgen/latest/trustfall_stubgen/ pub trait Adapter<'vertex> { /// The type of vertices in the dataset this adapter queries. - /// It's frequently a good idea to use an Rc<...> type for cheaper cloning here. + /// Unless your intended vertex type is cheap to clone, consider wrapping it an [`Rc`][rc] + /// or [`Arc`] to make cloning it cheaper since that's a fairly common operation + /// when queries are evaluated. + /// + /// [rc]: std::rc::Rc type Vertex: Clone + Debug + 'vertex; /// Produce an iterator of vertices for the specified starting edge. /// - /// Starting edges are ones where queries are allowed to begin. - /// They are defined directly on the root query type of the schema. - /// For example, `User` is the starting edge of the following query: + /// Starting edges are the entry points for querying according to a schema. + /// Each query starts at such an edge, and such starting edges are defined + /// directly on the root query type of the schema. + /// + /// # Example + /// + /// Consider this query which gets the URLs of the posts + /// currently on the front page of HackerNews: [playground][playground] /// ```graphql /// query { - /// User { - /// name @output - /// } + /// FrontPage { + /// url @output + /// } /// } /// ``` /// + /// The [HackerNews schema][schema] defines `FrontPage` as a starting edge + /// that points to vertices of type `Item`. + /// + /// As part of executing this query, Trustfall will call this method + /// with `edge_name = "FrontPage"`. Here's the [implementation of this method][method] + /// in the HackerNews example adapter. + /// + /// # Preconditions and postconditions + /// /// The caller guarantees that: /// - The specified edge is a starting edge in the schema being queried. /// - Any parameters the edge requires per the schema have values provided. + /// + /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-HackerNews-item-URLs-of-the-items*l*3-currently-on-the-front-page.*lquery---0FrontPage---2url-*o*l--_0*J*l*J&v=--0*l*J + /// [schema]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L35 + /// [method]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/adapter.rs#L127-L133 fn resolve_starting_vertices( &self, edge_name: &Arc, @@ -425,42 +524,114 @@ pub trait Adapter<'vertex> { resolve_info: &ResolveInfo, ) -> VertexIterator<'vertex, Self::Vertex>; - /// Resolve the value of a vertex property over an iterator of query contexts. + /// Resolve a property required by the query that's being evaluated. + /// + /// Each [`DataContext`] in the `contexts` parameter has an active vertex + /// [`DataContext::active_vertex()`]. This call is asking for the value of + /// the specified property on each such active vertex, + /// for each active vertex in the input iterator. + /// + /// The most ergonomic way to implement this method is usually via + /// the [`resolve_property_with()`][resolve-property] helper method together with + /// the [`field_property!()`][field-property] and [`accessor_property!()`][accessor-property] + /// macros. + /// + /// # Example + /// + /// Consider this query which gets the URLs of the posts + /// currently on the front page of HackerNews: [playground][playground] + /// ```graphql + /// query { + /// FrontPage { + /// url @output + /// } + /// } + /// ``` + /// + /// Our HackerNews schema [defines][starting-edge] `FrontPage` as a starting edge + /// that points to vertices of type `Item`, and [defines][property] `url` + /// as a property on the `Item` type. /// - /// Each [`DataContext`] in the `contexts` argument has an active vertex, - /// which is either `None`, or a `Some(Self::Vertex)` value representing a vertex - /// of type `type_name` defined in the schema. + /// As part of executing this query, Trustfall will call this method + /// with `type_name = "Item"` and `property_name = "url"`. + /// This is how Trustfall looks up the URLs of the items returned by this query. + /// Here's the [implementation of this method][method] in the HackerNews example adapter. /// - /// This function resolves the property value on that active vertex. + /// # Preconditions and postconditions + /// + /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex` + /// and represents a vertex whose type in the Trustfall schema is given by + /// this function's `type_name` parameter. /// /// The caller guarantees that: /// - `type_name` is a type or interface defined in the schema. /// - `property_name` is either a property field on `type_name` defined in the schema, /// or the special value `"__typename"` requesting the name of the vertex's type. - /// - When the active vertex is `Some(...)`, it's a vertex of type `type_name`: - /// either its type is exactly `type_name`, or `type_name` is an interface that - /// the vertex's type implements. + /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`: + /// either its type is exactly `type_name`, or `type_name` is an interface implemented by + /// the vertex's type. /// /// The returned iterator must satisfy these properties: /// - Produce `(context, property_value)` tuples with the property's value for that context. /// - Produce contexts in the same order as the input `contexts` iterator produced them. /// - Produce property values whose type matches the property's type defined in the schema. /// - When a context's active vertex is `None`, its property value is [`FieldValue::Null`]. - fn resolve_property( + /// + /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-HackerNews-item-URLs-of-the-items*l*3-currently-on-the-front-page.*lquery---0FrontPage---2url-*o*l--_0*J*l*J&v=--0*l*J + /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L35 + /// [property]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L44 + /// [method]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/adapter.rs#L151 + /// [resolve-property]: helpers::resolve_property_with + /// [field-property]: crate::field_property + /// [accessor-property]: crate::accessor_property + fn resolve_property + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, FieldValue>; + ) -> ContextOutcomeIterator<'vertex, V, FieldValue>; - /// Resolve the neighboring vertices across an edge, for each query context in an iterator. + /// Resolve the neighboring vertices across an edge. + /// + /// Each [`DataContext`] in the `contexts` parameter has an active vertex + /// [`DataContext::active_vertex()`]. This call is asking for + /// the iterator of neighboring vertices of the active vertex along a specified edge, + /// for each active vertex in the input iterator. + /// + /// The most ergonomic way to implement this method is usually via + /// the [`resolve_neighbors_with()`][resolve-neighbors] helper method. + /// + /// # Example /// - /// Each [`DataContext`] in the `contexts` argument has an active vertex, - /// which is either `None`, or a `Some(Self::Vertex)` value representing a vertex - /// of type `type_name` defined in the schema. + /// Consider this query which gets the usernames and karma points of the users + /// who submitted the latest stories on HackerNews: [playground][playground] + /// ```graphql + /// query { + /// Latest { + /// byUser { + /// id @output + /// karma @output + /// } + /// } + /// } + /// ``` /// - /// This function resolves the neighboring vertices for that active vertex. + /// Our HackerNews schema [defines][starting-edge] `Latest` as a starting edge + /// that points to vertices of type `Story`. + /// In turn, `Story` [has an edge][edge] called `byUser` that points to `User` vertices. + /// + /// As part of executing this query, Trustfall will call this method + /// with `type_name = "Story"` and `edge_name = "byUser"`. + /// This is how Trustfall looks up the user vertices representing the submitters + /// of the latest HackerNews stories. + /// Here's the [implementation of this method][method] in the HackerNews example adapter. + /// + /// # Preconditions and postconditions + /// + /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex` + /// and represents a vertex whose type in the Trustfall schema is given by + /// this function's `type_name` parameter. /// /// If the schema this adapter covers has no edges aside from starting edges, /// then this method will never be called and may be implemented as `unreachable!()`. @@ -468,45 +639,70 @@ pub trait Adapter<'vertex> { /// The caller guarantees that: /// - `type_name` is a type or interface defined in the schema. /// - `edge_name` is an edge field on `type_name` defined in the schema. - /// - Any parameters the edge requires per the schema have values provided. - /// - When the active vertex is `Some(...)`, it's a vertex of type `type_name`: - /// either its type is exactly `type_name`, or `type_name` is an interface that - /// the vertex's type implements. + /// - Each parameter required by the edge has a value of appropriate type, per the schema. + /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`: + /// either its type is exactly `type_name`, or `type_name` is an interface implemented by + /// the vertex's type. /// /// The returned iterator must satisfy these properties: /// - Produce `(context, neighbors)` tuples with an iterator of neighbor vertices for that edge. /// - Produce contexts in the same order as the input `contexts` iterator produced them. /// - Each neighboring vertex is of the type specified for that edge in the schema. /// - When a context's active vertex is None, it has an empty neighbors iterator. - fn resolve_neighbors( + /// + /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-usernames-and-karma-points-of-the-folks*l*3-who-submitted-the-latest-stories-on-HackerNews.*lquery---0Latest---2byUser---4id-*o*l--_4karma-*o*l--_2--*0*J*l*J&v=--0*l*J + /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L37 + /// [edge]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L73 + /// [method]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/adapter.rs#L223 + /// [resolve-neighbors]: helpers::resolve_neighbors_with + fn resolve_neighbors + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, VertexIterator<'vertex, Self::Vertex>>; + ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>>; - /// Attempt to coerce vertices to a subtype, over an iterator of query contexts. + /// Attempt to coerce vertices to a subtype, as required by the query that's being evaluated. /// - /// In this example query, the starting vertices of type `File` are coerced to `AudioFile`: + /// Each [`DataContext`] in the `contexts` parameter has an active vertex + /// [`DataContext::active_vertex()`]. This call is asking whether the active vertex + /// happens to be an instance of a subtype, for each active vertex in the input iterator. + /// + /// The most ergonomic ways to implement this method usually rely on + /// the [`resolve_coercion_using_schema()`][resolve-schema] + /// or [`resolve_coercion_with()`][resolve-basic] helper methods. + /// + /// # Example + /// + /// Consider this query which gets the titles of all stories on the front page of HackerNews, + /// while discarding non-story items such as job postings and polls: [playground][playground] /// ```graphql /// query { - /// File { - /// ... on AudioFile { - /// duration @output - /// } + /// FrontPage { + /// ... on Story { + /// title @output /// } + /// } /// } /// ``` - /// The `... on AudioFile` operator causes only `AudioFile` vertices to be retained, - /// filtering out all other kinds of `File` vertices. /// - /// Each [`DataContext`] in the `contexts` argument has an active vertex, - /// which is either `None`, or a `Some(Self::Vertex)` value representing a vertex - /// of type `type_name` defined in the schema. + /// Our HackerNews schema [defines][starting-edge] `FrontPage` as a starting edge + /// that points to vertices of type `Item`. + /// It also defines `Story` as [a subtype][subtype] of `Item`. + /// + /// After resolving the `FrontPage` starting edge, Trustfall will need to determine which + /// of the resulting `Item` vertices are actually of type `Story`. + /// This is when Trustfall will call this method + /// with `type_name = "Item"` and `coerce_to_type = "Story"`. + /// Here's the [implementation of this method][method] in the HackerNews example adapter. /// - /// This function checks whether the active vertex is of the specified subtype. + /// # Preconditions and postconditions + /// + /// The active vertex may be `None`, or a `Some(v)` whose `v` is of Rust type `&Self::Vertex` + /// and represents a vertex whose type in the Trustfall schema is given by + /// this function's `type_name` parameter. /// /// If this adapter's schema contains no subtyping, then no type coercions are possible: /// this method will never be called and may be implemented as `unreachable!()`. @@ -514,20 +710,74 @@ pub trait Adapter<'vertex> { /// The caller guarantees that: /// - `type_name` is an interface defined in the schema. /// - `coerce_to_type` is a type or interface that implements `type_name` in the schema. - /// - When the active vertex is `Some(...)`, it's a vertex of type `type_name`: - /// either its type is exactly `type_name`, or `type_name` is an interface that - /// the vertex's type implements. + /// - When the active vertex is `Some(...)`, its represents a vertex of type `type_name`: + /// either its type is exactly `type_name`, or `type_name` is an interface implemented by + /// the vertex's type. /// /// The returned iterator must satisfy these properties: /// - Produce `(context, can_coerce)` tuples showing if the coercion succeded for that context. /// - Produce contexts in the same order as the input `contexts` iterator produced them. /// - Each neighboring vertex is of the type specified for that edge in the schema. /// - When a context's active vertex is `None`, its coercion outcome is `false`. - fn resolve_coercion( + /// + /// [playground]: https://play.predr.ag/hackernews#?f=2&q=*3-Get-the-title-of-stories-on-the-HN-front-page.*l*3-Discards-any-non*-story-items-on-the-front-page*L*l*3-such-as-job-postings-or-polls.*lquery---0FrontPage---2*E-Story---4title-*o*l--_2--*0*J*l*J&v=--0*l*J + /// [starting-edge]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L35 + /// [subtype]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/hackernews.graphql#L58 + /// [method]: https://github.com/obi1kenobi/trustfall/blob/main/trustfall/examples/hackernews/adapter.rs#L375 + /// [resolve-schema]: helpers::resolve_coercion_using_schema + /// [resolve-basic]: helpers::resolve_coercion_with + fn resolve_coercion + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, bool>; + ) -> ContextOutcomeIterator<'vertex, V, bool>; +} + +/// Attempt to dereference a value to a `&V`, returning `None` if the value did not contain a `V`. +/// +/// This trait allows types that may contain a `V` to be projected down to a `Option<&V>`. +/// It's similar in spirit to the built-in [`Deref`][deref] trait. +/// The primary difference is that [`AsVertex`] does not guarantee it'll be able to produce a `&V`, +/// instead returning `Option<&V>`. The same type may implement [`AsVertex`] multiple times +/// with different `V` types, also unlike [`Deref`][deref]. +/// +/// [deref]: https://doc.rust-lang.org/std/ops/trait.Deref.html +pub trait AsVertex: Debug + Clone { + /// Dereference this value into a `&V`, if the value happens to contain a `V`. + /// + /// If this method returns `Some(&v)`, [`AsVertex::into_vertex()`] for the same `V` + /// is guaranteed to return `Some(v)` as well. + fn as_vertex(&self) -> Option<&V>; + + /// Consume `self` and produce the contained `V`, if one was indeed present. + /// + /// If this method returned `Some(v)`, prior [`AsVertex::as_vertex()`] calls for the same `V` + /// are guaranteed to have returned `Some(&v)` as well. + fn into_vertex(self) -> Option; +} + +/// Allow bidirectional conversions between a type `V` and the type implementing this trait. +/// +/// Values of type `V` may be converted into the type implementing this trait, and values +/// of the implementing type may be converted into `V` via the `AsVertex` supertrait. +pub trait Cast: AsVertex { + /// Convert a `V` into the type implementing this trait. + /// + /// This is the inverse of [`AsVertex::into_vertex()`]: this function converts `vertex: V` + /// into `Self` whereas [`AsVertex::into_vertex()`] can convert the resulting `Self` + /// back into `Some(v)`. + fn into_self(vertex: V) -> Self; +} + +/// Trivially, every `Debug + Clone` type is [`AsVertex`] of itself. +impl AsVertex for V { + fn as_vertex(&self) -> Option<&V> { + Some(self) + } + + fn into_vertex(self) -> Option { + Some(self) + } } diff --git a/trustfall_core/src/interpreter/replay.rs b/trustfall_core/src/interpreter/replay.rs index 1f523077..82384283 100644 --- a/trustfall_core/src/interpreter/replay.rs +++ b/trustfall_core/src/interpreter/replay.rs @@ -18,8 +18,8 @@ use crate::{ use super::{ execution::interpret_ir, trace::{FunctionCall, Opid, Trace, TraceOp, TraceOpContent, YieldValue}, - Adapter, ContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, ResolveInfo, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, + ResolveInfo, VertexIterator, }; #[derive(Clone, Debug)] @@ -79,23 +79,24 @@ where } } -struct TraceReaderResolvePropertiesIter<'trace, Vertex> +struct TraceReaderResolvePropertiesIter<'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'trace, { exhausted: bool, parent_opid: Opid, - contexts: ContextIterator<'trace, Vertex>, - input_batch: VecDeque>, + contexts: ContextIterator<'trace, V>, + input_batch: VecDeque>, inner: Rc>>>, } #[allow(unused_variables)] -impl<'trace, Vertex> Iterator for TraceReaderResolvePropertiesIter<'trace, Vertex> +impl<'trace, V, Vertex> Iterator for TraceReaderResolvePropertiesIter<'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'trace, + V: AsVertex, { - type Item = (DataContext, FieldValue); + type Item = (DataContext, FieldValue); fn next(&mut self) -> Option { assert!(!self.exhausted); @@ -125,10 +126,14 @@ where if let TraceOpContent::YieldInto(context) = &input_op.content { let input_context = input_data.unwrap(); - assert_eq!(context, &input_context, "at {input_op:?}"); + assert_eq!( + context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {input_op:?}" + ); self.input_batch.push_back(input_context); } else if let TraceOpContent::InputIteratorExhausted = &input_op.content { - assert_eq!(None, input_data, "at {input_op:?}"); + assert!(input_data.is_none(), "at {input_op:?}"); } else { unreachable!(); } @@ -140,11 +145,15 @@ where match &next_op.content { TraceOpContent::YieldFrom(YieldValue::ResolveProperty(trace_context, value)) => { let input_context = self.input_batch.pop_front().unwrap(); - assert_eq!(trace_context, &input_context, "at {next_op:?}"); + assert_eq!( + trace_context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {next_op:?}" + ); Some((input_context, value.clone())) } TraceOpContent::OutputIteratorExhausted => { - assert_eq!(None, self.input_batch.pop_front(), "at {next_op:?}"); + assert!(self.input_batch.pop_front().is_none(), "at {next_op:?}"); self.exhausted = true; None } @@ -153,25 +162,28 @@ where } } -struct TraceReaderResolveCoercionIter<'query, 'trace, Vertex> +struct TraceReaderResolveCoercionIter<'query, 'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'query, + V: AsVertex, 'trace: 'query, { exhausted: bool, parent_opid: Opid, - contexts: ContextIterator<'query, Vertex>, - input_batch: VecDeque>, + contexts: ContextIterator<'query, V>, + input_batch: VecDeque>, inner: Rc>>>, } #[allow(unused_variables)] -impl<'query, 'trace, Vertex> Iterator for TraceReaderResolveCoercionIter<'query, 'trace, Vertex> +impl<'query, 'trace, V, Vertex> Iterator + for TraceReaderResolveCoercionIter<'query, 'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'query, + V: AsVertex, 'trace: 'query, { - type Item = (DataContext, bool); + type Item = (DataContext, bool); fn next(&mut self) -> Option { assert!(!self.exhausted); @@ -201,11 +213,15 @@ where if let TraceOpContent::YieldInto(context) = &input_op.content { let input_context = input_data.unwrap(); - assert_eq!(context, &input_context, "at {input_op:?}"); + assert_eq!( + context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {input_op:?}" + ); self.input_batch.push_back(input_context); } else if let TraceOpContent::InputIteratorExhausted = &input_op.content { - assert_eq!(None, input_data, "at {input_op:?}"); + assert!(input_data.is_none(), "at {input_op:?}"); } else { unreachable!(); } @@ -217,11 +233,15 @@ where match &next_op.content { TraceOpContent::YieldFrom(YieldValue::ResolveCoercion(trace_context, can_coerce)) => { let input_context = self.input_batch.pop_front().unwrap(); - assert_eq!(trace_context, &input_context, "at {next_op:?}"); + assert_eq!( + trace_context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {next_op:?}" + ); Some((input_context, *can_coerce)) } TraceOpContent::OutputIteratorExhausted => { - assert_eq!(None, self.input_batch.pop_front(), "at {next_op:?}"); + assert!(self.input_batch.pop_front().is_none(), "at {next_op:?}"); self.exhausted = true; None } @@ -230,24 +250,27 @@ where } } -struct TraceReaderResolveNeighborsIter<'query, 'trace, Vertex> +struct TraceReaderResolveNeighborsIter<'query, 'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'query, + V: AsVertex, 'trace: 'query, { exhausted: bool, parent_opid: Opid, - contexts: ContextIterator<'query, Vertex>, - input_batch: VecDeque>, + contexts: ContextIterator<'query, V>, + input_batch: VecDeque>, inner: Rc>>>, } -impl<'query, 'trace, Vertex> Iterator for TraceReaderResolveNeighborsIter<'query, 'trace, Vertex> +impl<'query, 'trace, V, Vertex> Iterator + for TraceReaderResolveNeighborsIter<'query, 'trace, V, Vertex> where Vertex: Clone + Debug + PartialEq + Eq + Serialize + DeserializeOwned + 'query, + V: AsVertex, 'trace: 'query, { - type Item = (DataContext, VertexIterator<'query, Vertex>); + type Item = (DataContext, VertexIterator<'query, Vertex>); fn next(&mut self) -> Option { assert!(!self.exhausted); @@ -277,11 +300,15 @@ where if let TraceOpContent::YieldInto(context) = &input_op.content { let input_context = input_data.unwrap(); - assert_eq!(context, &input_context, "at {input_op:?}"); + assert_eq!( + context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {input_op:?}" + ); self.input_batch.push_back(input_context); } else if let TraceOpContent::InputIteratorExhausted = &input_op.content { - assert_eq!(None, input_data, "at {input_op:?}"); + assert!(input_data.is_none(), "at {input_op:?}"); } else { unreachable!(); } @@ -293,7 +320,11 @@ where match &next_op.content { TraceOpContent::YieldFrom(YieldValue::ResolveNeighborsOuter(trace_context)) => { let input_context = self.input_batch.pop_front().unwrap(); - assert_eq!(trace_context, &input_context, "at {next_op:?}"); + assert_eq!( + trace_context, + &input_context.clone().flat_map(&mut |v| v.into_vertex()), + "at {next_op:?}" + ); let neighbors = Box::new(TraceReaderNeighborIter { exhausted: false, @@ -305,7 +336,7 @@ where Some((input_context, neighbors)) } TraceOpContent::OutputIteratorExhausted => { - assert_eq!(None, self.input_batch.pop_front(), "at {next_op:?}"); + assert!(self.input_batch.pop_front().is_none(), "at {next_op:?}"); self.exhausted = true; None } @@ -390,13 +421,13 @@ where } } - fn resolve_property( + fn resolve_property + 'trace>( &self, - contexts: ContextIterator<'trace, Self::Vertex>, + contexts: ContextIterator<'trace, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'trace, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'trace, V, FieldValue> { let (root_opid, trace_op) = advance_ref_iter(self.next_op.as_ref()) .expect("Expected a resolve_property() call operation, but found none."); assert_eq!(None, trace_op.parent_opid); @@ -420,14 +451,14 @@ where } } - fn resolve_neighbors( + fn resolve_neighbors + 'trace>( &self, - contexts: ContextIterator<'trace, Self::Vertex>, + contexts: ContextIterator<'trace, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'trace, Self::Vertex, VertexIterator<'trace, Self::Vertex>> { + ) -> ContextOutcomeIterator<'trace, V, VertexIterator<'trace, Self::Vertex>> { let (root_opid, trace_op) = advance_ref_iter(self.next_op.as_ref()) .expect("Expected a resolve_property() call operation, but found none."); assert_eq!(None, trace_op.parent_opid); @@ -451,13 +482,13 @@ where } } - fn resolve_coercion( + fn resolve_coercion + 'trace>( &self, - contexts: ContextIterator<'trace, Self::Vertex>, + contexts: ContextIterator<'trace, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'trace, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'trace, V, bool> { let (root_opid, trace_op) = advance_ref_iter(self.next_op.as_ref()) .expect("Expected a resolve_coercion() call operation, but found none."); assert_eq!(None, trace_op.parent_opid); diff --git a/trustfall_core/src/interpreter/trace.rs b/trustfall_core/src/interpreter/trace.rs index eb3c518c..663ad7f2 100644 --- a/trustfall_core/src/interpreter/trace.rs +++ b/trustfall_core/src/interpreter/trace.rs @@ -12,7 +12,7 @@ use crate::{ }; use super::{ - ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, VertexInfo, + AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, VertexInfo, VertexIterator, }; @@ -149,6 +149,14 @@ fn make_iter_with_pre_action, F: Fn()>( PreActionIter { inner, pre_action } } +/// An adapter "middleware" that records all adapter operations into a linear, replayable trace. +/// +/// Tapping adapters must be done at top level, on the top-level adapter which is being used +/// for query execution i.e. where the `` generic on the resolver methods +/// is the same as `AdapterT::Vertex`. +/// +/// Otherwise, the recorded traces may not be possible to replay since they would be incomplete: +/// they would only capture a portion of the execution, the rest of which is missing. #[derive(Debug, Clone)] pub struct AdapterTap<'vertex, AdapterT> where @@ -236,13 +244,13 @@ where ) } - fn resolve_property( + fn resolve_property + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'vertex, V, FieldValue> { let mut trace = self.tracer.borrow_mut(); let call_opid = trace.record( TraceOpContent::Call(FunctionCall::ResolveProperty( @@ -271,9 +279,10 @@ where }, ) .map(move |context| { - tracer_ref_3 - .borrow_mut() - .record(TraceOpContent::YieldInto(context.clone()), Some(call_opid)); + tracer_ref_3.borrow_mut().record( + TraceOpContent::YieldInto(context.clone().flat_map(&mut |v| v.into_vertex())), + Some(call_opid), + ); context }), ); @@ -291,7 +300,7 @@ where .map(move |(context, value)| { tracer_ref_5.borrow_mut().record( TraceOpContent::YieldFrom(YieldValue::ResolveProperty( - context.clone(), + context.clone().flat_map(&mut |v| v.into_vertex()), value.clone(), )), Some(call_opid), @@ -302,14 +311,14 @@ where ) } - fn resolve_neighbors( + fn resolve_neighbors + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, VertexIterator<'vertex, Self::Vertex>> { + ) -> ContextOutcomeIterator<'vertex, V, VertexIterator<'vertex, Self::Vertex>> { let mut trace = self.tracer.borrow_mut(); let call_opid = trace.record( TraceOpContent::Call(FunctionCall::ResolveNeighbors( @@ -338,9 +347,10 @@ where }, ) .map(move |context| { - tracer_ref_3 - .borrow_mut() - .record(TraceOpContent::YieldInto(context.clone()), Some(call_opid)); + tracer_ref_3.borrow_mut().record( + TraceOpContent::YieldInto(context.clone().flat_map(&mut |v| v.into_vertex())), + Some(call_opid), + ); context }), ); @@ -363,7 +373,9 @@ where .map(move |(context, neighbor_iter)| { let mut trace = tracer_ref_5.borrow_mut(); let outer_iterator_opid = trace.record( - TraceOpContent::YieldFrom(YieldValue::ResolveNeighborsOuter(context.clone())), + TraceOpContent::YieldFrom(YieldValue::ResolveNeighborsOuter( + context.clone().flat_map(&mut |v| v.into_vertex()), + )), Some(call_opid), ); drop(trace); @@ -395,13 +407,13 @@ where ) } - fn resolve_coercion( + fn resolve_coercion + 'vertex>( &self, - contexts: ContextIterator<'vertex, Self::Vertex>, + contexts: ContextIterator<'vertex, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'vertex, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'vertex, V, bool> { let mut trace = self.tracer.borrow_mut(); let call_opid = trace.record( TraceOpContent::Call(FunctionCall::ResolveCoercion( @@ -430,9 +442,10 @@ where }, ) .map(move |context| { - tracer_ref_3 - .borrow_mut() - .record(TraceOpContent::YieldInto(context.clone()), Some(call_opid)); + tracer_ref_3.borrow_mut().record( + TraceOpContent::YieldInto(context.clone().flat_map(&mut |v| v.into_vertex())), + Some(call_opid), + ); context }), ); @@ -450,7 +463,7 @@ where .map(move |(context, can_coerce)| { tracer_ref_5.borrow_mut().record( TraceOpContent::YieldFrom(YieldValue::ResolveCoercion( - context.clone(), + context.clone().flat_map(&mut |v| v.into_vertex()), can_coerce, )), Some(call_opid), diff --git a/trustfall_core/src/nullables_interpreter.rs b/trustfall_core/src/nullables_interpreter.rs index 13ee7cd1..59b21807 100644 --- a/trustfall_core/src/nullables_interpreter.rs +++ b/trustfall_core/src/nullables_interpreter.rs @@ -29,34 +29,34 @@ impl<'a> Adapter<'a> for NullablesAdapter { unimplemented!() } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { unimplemented!() } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { unimplemented!() } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { unimplemented!() } } diff --git a/trustfall_core/src/numbers_interpreter.rs b/trustfall_core/src/numbers_interpreter.rs index 460e2f3e..0b79d787 100644 --- a/trustfall_core/src/numbers_interpreter.rs +++ b/trustfall_core/src/numbers_interpreter.rs @@ -8,8 +8,8 @@ use crate::{ interpreter::{ self, helpers::{resolve_coercion_with, resolve_neighbors_with, resolve_property_with}, - Adapter, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, Typename, - VertexIterator, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, + Typename, VertexIterator, }, ir::{EdgeParameters, FieldValue}, schema::Schema, @@ -272,13 +272,13 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { return interpreter::helpers::resolve_typename(contexts, &self.schema, type_name); } @@ -299,14 +299,14 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { let mut primes = btreeset![2, 3]; let parameters = parameters.clone(); match (type_name.as_ref(), edge_name.as_ref()) { @@ -414,13 +414,13 @@ impl<'a> Adapter<'a> for NumbersAdapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { match (type_name.as_ref(), coerce_to_type.as_ref()) { ("Number" | "Named", "Prime") => { resolve_coercion_with(contexts, |vertex| matches!(vertex, NumbersVertex::Prime(..))) diff --git a/trustfall_core/src/schema/adapter/mod.rs b/trustfall_core/src/schema/adapter/mod.rs index 7fb7177c..f1837b8d 100644 --- a/trustfall_core/src/schema/adapter/mod.rs +++ b/trustfall_core/src/schema/adapter/mod.rs @@ -8,8 +8,8 @@ use crate::{ accessor_property, field_property, interpreter::{ helpers::{resolve_neighbors_with, resolve_property_with}, - CandidateValue, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, ResolveInfo, - Typename, VertexInfo, VertexIterator, + AsVertex, CandidateValue, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, + ResolveInfo, Typename, VertexInfo, VertexIterator, }, ir::{EdgeParameters, FieldValue, TransparentValue, Type}, }; @@ -295,15 +295,17 @@ impl<'a> crate::interpreter::Adapter<'a> for SchemaAdapter<'a> { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { - return resolve_property_with(contexts, |vertex| vertex.typename().into()); + return resolve_property_with::(contexts, |vertex| { + vertex.typename().into() + }); } match type_name.as_ref() { @@ -371,14 +373,14 @@ impl<'a> crate::interpreter::Adapter<'a> for SchemaAdapter<'a> { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, _parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { let schema = self.schema; match type_name.as_ref() { "VertexType" => match edge_name.as_ref() { @@ -445,13 +447,13 @@ impl<'a> crate::interpreter::Adapter<'a> for SchemaAdapter<'a> { } #[allow(unused_variables)] - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, coerce_to_type: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { unreachable!("unexpected type coercion: {type_name} -> {coerce_to_type}") } } diff --git a/trustfall_stubgen/src/adapter_creator.rs b/trustfall_stubgen/src/adapter_creator.rs index b03e6cc7..f55ae176 100644 --- a/trustfall_stubgen/src/adapter_creator.rs +++ b/trustfall_stubgen/src/adapter_creator.rs @@ -155,6 +155,7 @@ fn emit_property_handling( } builtin_imports.insert(parse_import("std::sync::Arc")); + external_imports.insert(parse_import("trustfall::provider::AsVertex")); external_imports.insert(parse_import("trustfall::provider::ContextIterator")); external_imports.insert(parse_import("trustfall::provider::ContextOutcomeIterator")); external_imports.insert(parse_import("trustfall::provider::ResolveInfo")); @@ -163,13 +164,13 @@ fn emit_property_handling( external_imports.insert(parse_import("trustfall::provider::resolve_property_with")); quote! { - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { return resolve_property_with(contexts, |vertex| vertex.typename().into()); } @@ -221,6 +222,7 @@ fn emit_edge_handling( } builtin_imports.insert(parse_import("std::sync::Arc")); + external_imports.insert(parse_import("trustfall::provider::AsVertex")); external_imports.insert(parse_import("trustfall::provider::ContextIterator")); external_imports.insert(parse_import("trustfall::provider::ContextOutcomeIterator")); external_imports.insert(parse_import("trustfall::provider::EdgeParameters")); @@ -229,14 +231,14 @@ fn emit_edge_handling( external_imports.insert(parse_import("trustfall::FieldValue")); quote! { - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match type_name.as_ref() { #arms _ => unreachable!("attempted to resolve edge '{edge_name}' on unexpected type: {type_name}"), @@ -250,19 +252,20 @@ fn emit_coercion_handling( external_imports: &mut BTreeSet>, ) -> proc_macro2::TokenStream { builtin_imports.insert(parse_import("std::sync::Arc")); + external_imports.insert(parse_import("trustfall::provider::AsVertex")); external_imports.insert(parse_import("trustfall::provider::ContextIterator")); external_imports.insert(parse_import("trustfall::provider::ContextOutcomeIterator")); external_imports.insert(parse_import("trustfall::provider::ResolveInfo")); external_imports.insert(parse_import("trustfall::provider::resolve_coercion_using_schema")); quote! { - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, _type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { resolve_coercion_using_schema(contexts, Self::schema(), coerce_to_type.as_ref()) } } diff --git a/trustfall_stubgen/src/edges_creator.rs b/trustfall_stubgen/src/edges_creator.rs index 512c130e..86bbc084 100644 --- a/trustfall_stubgen/src/edges_creator.rs +++ b/trustfall_stubgen/src/edges_creator.rs @@ -67,6 +67,7 @@ pub(super) fn make_edges_file( edges_file.top_level_items.push(type_edge_mod); } + edges_file.external_imports.insert(parse_import("trustfall::provider::AsVertex")); edges_file.external_imports.insert(parse_import("trustfall::provider::ContextIterator")); edges_file.external_imports.insert(parse_import("trustfall::provider::ContextOutcomeIterator")); edges_file.external_imports.insert(parse_import("trustfall::provider::EdgeParameters")); @@ -100,12 +101,12 @@ fn make_type_edge_resolver( let unreachable_msg = format!("attempted to resolve unexpected edge '{{edge_name}}' on type '{type_name}'"); let type_edge_resolver = quote! { - pub(super) fn #ident<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn #ident<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { #arms _ => unreachable!(#unreachable_msg), @@ -116,7 +117,7 @@ fn make_type_edge_resolver( let type_edge_mod = quote! { mod #mod_name { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, VertexIterator, }; @@ -150,11 +151,11 @@ fn make_edge_resolver_and_call( let expect_msg = format!("conversion failed, vertex was not a {type_name}"); let todo_msg = format!("get neighbors along edge '{edge_name}' for type '{type_name}'"); let resolver = quote! { - pub(super) fn #resolver_fn_ident<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn #resolver_fn_ident<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, #fn_params _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with(contexts, |vertex| { let vertex = vertex.#conversion_fn_ident().expect(#expect_msg); todo!(#todo_msg) diff --git a/trustfall_stubgen/src/properties_creator.rs b/trustfall_stubgen/src/properties_creator.rs index 8a41baa0..2348498a 100644 --- a/trustfall_stubgen/src/properties_creator.rs +++ b/trustfall_stubgen/src/properties_creator.rs @@ -51,6 +51,7 @@ pub(super) fn make_properties_file( properties_file.top_level_items.push(resolver); } + properties_file.external_imports.insert(parse_import("trustfall::provider::AsVertex")); properties_file.external_imports.insert(parse_import("trustfall::provider::ContextIterator")); properties_file .external_imports @@ -76,11 +77,11 @@ fn make_resolver_fn(type_name: &str, properties: &[String]) -> proc_macro2::Toke let unreachable_msg = format!("attempted to read unexpected property '{{property_name}}' on type '{type_name}'"); quote! { - pub(super) fn #ident<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn #ident<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { #arms _ => unreachable!(#unreachable_msg), diff --git a/trustfall_stubgen/src/tests.rs b/trustfall_stubgen/src/tests.rs index 237c918c..492062e5 100644 --- a/trustfall_stubgen/src/tests.rs +++ b/trustfall_stubgen/src/tests.rs @@ -16,22 +16,39 @@ fn write_new_file(path: &Path, contents: &str) { } fn assert_generated_code_compiles(path: &Path) { - let cargo_toml = r#" + let output = std::process::Command::new(env!("CARGO")) + .arg("locate-project") + .arg("--workspace") + .arg("--message-format=plain") + .output() + .expect("error running `cargo locate-project`") + .stdout; + let workspace_cargo_toml_path = + Path::new(std::str::from_utf8(&output).expect("not legal utf-8").trim()); + + let trustfall_lib_path = + workspace_cargo_toml_path.parent().expect("no parent path").join("trustfall"); + + let trustfall_lib_str = trustfall_lib_path.display(); + + let cargo_toml = format!( + " [package] -name = "tests" +name = \"tests\" publish = false -version = "0.1.0" -edition = "2021" -rust-version = "1.70" +version = \"0.1.0\" +edition = \"2021\" +rust-version = \"1.70\" [dependencies] -trustfall = "*" +trustfall = {{ path = \"{trustfall_lib_str}\" }} [workspace] -"#; +" + ); let mut cargo_toml_path = PathBuf::from(path); cargo_toml_path.push("Cargo.toml"); - write_new_file(cargo_toml_path.as_path(), cargo_toml); + write_new_file(cargo_toml_path.as_path(), &cargo_toml); let lib_rs = "\ mod adapter; diff --git a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/adapter_impl.rs b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/adapter_impl.rs index 81703256..279640c8 100644 --- a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/adapter_impl.rs +++ b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/adapter_impl.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, OnceLock}; -use trustfall::{FieldValue, Schema, provider::{ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, Typename, VertexIterator, resolve_coercion_using_schema, resolve_property_with}}; +use trustfall::{FieldValue, Schema, provider::{AsVertex, ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, Typename, VertexIterator, resolve_coercion_using_schema, resolve_property_with}}; use super::vertex::Vertex; @@ -164,13 +164,13 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { return resolve_property_with(contexts, |vertex| vertex.typename().into()); } @@ -225,14 +225,14 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match type_name.as_ref() { "Comment" => { super::edges::resolve_comment_edge( @@ -274,13 +274,13 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, _type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { resolve_coercion_using_schema(contexts, Self::schema(), coerce_to_type.as_ref()) } } diff --git a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/edges.rs b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/edges.rs index 6dded56d..2c1a8284 100644 --- a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/edges.rs +++ b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/edges.rs @@ -1,13 +1,13 @@ -use trustfall::provider::{ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, VertexIterator}; +use trustfall::provider::{AsVertex, ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, VertexIterator}; use super::vertex::Vertex; -pub(super) fn resolve_comment_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_comment_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "byUser" => comment::by_user(contexts, resolve_info), "link" => comment::link(contexts, resolve_info), @@ -23,16 +23,16 @@ pub(super) fn resolve_comment_edge<'a>( mod comment { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn by_user<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn by_user<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -44,10 +44,10 @@ mod comment { ) } - pub(super) fn link<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn link<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -59,10 +59,10 @@ mod comment { ) } - pub(super) fn parent<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn parent<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -74,10 +74,10 @@ mod comment { ) } - pub(super) fn reply<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn reply<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -90,12 +90,12 @@ mod comment { } } -pub(super) fn resolve_job_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_job_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "link" => job::link(contexts, resolve_info), _ => { @@ -108,16 +108,16 @@ pub(super) fn resolve_job_edge<'a>( mod job { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn link<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn link<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -130,12 +130,12 @@ mod job { } } -pub(super) fn resolve_story_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_story_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "byUser" => story::by_user(contexts, resolve_info), "comment" => story::comment(contexts, resolve_info), @@ -150,16 +150,16 @@ pub(super) fn resolve_story_edge<'a>( mod story { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn by_user<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn by_user<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -171,10 +171,10 @@ mod story { ) } - pub(super) fn comment<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn comment<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -186,10 +186,10 @@ mod story { ) } - pub(super) fn link<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn link<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -202,12 +202,12 @@ mod story { } } -pub(super) fn resolve_user_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_user_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "link" => user::link(contexts, resolve_info), "submitted" => user::submitted(contexts, resolve_info), @@ -221,16 +221,16 @@ pub(super) fn resolve_user_edge<'a>( mod user { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn link<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn link<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -242,10 +242,10 @@ mod user { ) } - pub(super) fn submitted<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn submitted<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { diff --git a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/properties.rs b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/properties.rs index e9ae1dfd..72fe7c3e 100644 --- a/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/properties.rs +++ b/trustfall_stubgen/test_data/expected_outputs/hackernews/adapter/properties.rs @@ -1,12 +1,12 @@ -use trustfall::{FieldValue, provider::{ContextIterator, ContextOutcomeIterator, ResolveInfo}}; +use trustfall::{FieldValue, provider::{AsVertex, ContextIterator, ContextOutcomeIterator, ResolveInfo}}; use super::vertex::Vertex; -pub(super) fn resolve_comment_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_comment_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "byUsername" => { todo!("implement property 'byUsername' in fn `resolve_comment_property()`") @@ -30,11 +30,11 @@ pub(super) fn resolve_comment_property<'a>( } } -pub(super) fn resolve_item_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_item_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "id" => todo!("implement property 'id' in fn `resolve_item_property()`"), "unixTime" => { @@ -49,11 +49,11 @@ pub(super) fn resolve_item_property<'a>( } } -pub(super) fn resolve_job_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_job_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "id" => todo!("implement property 'id' in fn `resolve_job_property()`"), "score" => todo!("implement property 'score' in fn `resolve_job_property()`"), @@ -73,11 +73,11 @@ pub(super) fn resolve_job_property<'a>( } } -pub(super) fn resolve_story_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_story_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "byUsername" => { todo!("implement property 'byUsername' in fn `resolve_story_property()`") @@ -106,11 +106,11 @@ pub(super) fn resolve_story_property<'a>( } } -pub(super) fn resolve_user_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_user_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "aboutHtml" => { todo!("implement property 'aboutHtml' in fn `resolve_user_property()`") @@ -132,11 +132,11 @@ pub(super) fn resolve_user_property<'a>( } } -pub(super) fn resolve_webpage_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_webpage_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "url" => todo!("implement property 'url' in fn `resolve_webpage_property()`"), _ => { diff --git a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/adapter_impl.rs b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/adapter_impl.rs index 7581adee..87e76178 100644 --- a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/adapter_impl.rs +++ b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/adapter_impl.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, OnceLock}; -use trustfall::{FieldValue, Schema, provider::{ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, Typename, VertexIterator, resolve_coercion_using_schema, resolve_property_with}}; +use trustfall::{FieldValue, Schema, provider::{AsVertex, ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, ResolveInfo, Typename, VertexIterator, resolve_coercion_using_schema, resolve_property_with}}; use super::vertex::Vertex; @@ -61,13 +61,13 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_property( + fn resolve_property + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, property_name: &Arc, resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, FieldValue> { + ) -> ContextOutcomeIterator<'a, V, FieldValue> { if property_name.as_ref() == "__typename" { return resolve_property_with(contexts, |vertex| vertex.typename().into()); } @@ -143,14 +143,14 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_neighbors( + fn resolve_neighbors + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, type_name: &Arc, edge_name: &Arc, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, VertexIterator<'a, Self::Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Self::Vertex>> { match type_name.as_ref() { "const" => { super::edges::resolve_const_edge( @@ -232,13 +232,13 @@ impl<'a> trustfall::provider::Adapter<'a> for Adapter { } } - fn resolve_coercion( + fn resolve_coercion + 'a>( &self, - contexts: ContextIterator<'a, Self::Vertex>, + contexts: ContextIterator<'a, V>, _type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'a, Self::Vertex, bool> { + ) -> ContextOutcomeIterator<'a, V, bool> { resolve_coercion_using_schema(contexts, Self::schema(), coerce_to_type.as_ref()) } } diff --git a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/edges.rs b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/edges.rs index aa95459e..613073d2 100644 --- a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/edges.rs +++ b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/edges.rs @@ -1,13 +1,13 @@ -use trustfall::provider::{ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, VertexIterator}; +use trustfall::provider::{AsVertex, ContextIterator, ContextOutcomeIterator, EdgeParameters, ResolveEdgeInfo, VertexIterator}; use super::vertex::Vertex; -pub(super) fn resolve_const_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_const_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "const" => const_::const_(contexts, resolve_info), _ => { @@ -20,16 +20,16 @@ pub(super) fn resolve_const_edge<'a>( mod const_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn const_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn const_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -42,12 +42,12 @@ mod const_ { } } -pub(super) fn resolve_continue_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_continue_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "continue" => continue_::continue_(contexts, resolve_info), _ => { @@ -60,16 +60,16 @@ pub(super) fn resolve_continue_edge<'a>( mod continue_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn continue_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn continue_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -82,12 +82,12 @@ mod continue_ { } } -pub(super) fn resolve_dyn_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_dyn_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "dyn" => dyn_::dyn_(contexts, resolve_info), _ => { @@ -100,16 +100,16 @@ pub(super) fn resolve_dyn_edge<'a>( mod dyn_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn dyn_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn dyn_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -122,12 +122,12 @@ mod dyn_ { } } -pub(super) fn resolve_if_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_if_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "if" => if_::if_(contexts, resolve_info), _ => { @@ -140,16 +140,16 @@ pub(super) fn resolve_if_edge<'a>( mod if_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn if_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn if_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -162,12 +162,12 @@ mod if_ { } } -pub(super) fn resolve_mod_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_mod_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "mod" => mod_::mod_(contexts, resolve_info), _ => { @@ -180,16 +180,16 @@ pub(super) fn resolve_mod_edge<'a>( mod mod_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn mod_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn mod_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -202,12 +202,12 @@ mod mod_ { } } -pub(super) fn resolve_self_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_self_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "self" => self_::self_(contexts, resolve_info), _ => { @@ -220,16 +220,16 @@ pub(super) fn resolve_self_edge<'a>( mod self_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn self_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn self_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -242,12 +242,12 @@ mod self_ { } } -pub(super) fn resolve_type_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_type_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "type" => type_::type_(contexts, resolve_info), _ => { @@ -260,16 +260,16 @@ pub(super) fn resolve_type_edge<'a>( mod type_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn type_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn type_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -282,12 +282,12 @@ mod type_ { } } -pub(super) fn resolve_unsafe_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_unsafe_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "unsafe" => unsafe_::unsafe_(contexts, resolve_info), _ => { @@ -300,16 +300,16 @@ pub(super) fn resolve_unsafe_edge<'a>( mod unsafe_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn unsafe_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn unsafe_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { @@ -322,12 +322,12 @@ mod unsafe_ { } } -pub(super) fn resolve_where_edge<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_where_edge<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, edge_name: &str, parameters: &EdgeParameters, resolve_info: &ResolveEdgeInfo, -) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { +) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { match edge_name { "where" => where_::where_(contexts, resolve_info), _ => { @@ -340,16 +340,16 @@ pub(super) fn resolve_where_edge<'a>( mod where_ { use trustfall::provider::{ - resolve_neighbors_with, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo, - VertexIterator, + resolve_neighbors_with, AsVertex, ContextIterator, ContextOutcomeIterator, + ResolveEdgeInfo, VertexIterator, }; use super::super::vertex::Vertex; - pub(super) fn where_<'a>( - contexts: ContextIterator<'a, Vertex>, + pub(super) fn where_<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'a, Vertex, VertexIterator<'a, Vertex>> { + ) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex>> { resolve_neighbors_with( contexts, |vertex| { diff --git a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/properties.rs b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/properties.rs index e74c6807..32b21430 100644 --- a/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/properties.rs +++ b/trustfall_stubgen/test_data/expected_outputs/use_reserved_rust_names_in_schema/adapter/properties.rs @@ -1,12 +1,12 @@ -use trustfall::{FieldValue, provider::{ContextIterator, ContextOutcomeIterator, ResolveInfo}}; +use trustfall::{FieldValue, provider::{AsVertex, ContextIterator, ContextOutcomeIterator, ResolveInfo}}; use super::vertex::Vertex; -pub(super) fn resolve_const2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_const2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "const" => todo!("implement property 'const' in fn `resolve_const2_property()`"), _ => { @@ -17,11 +17,11 @@ pub(super) fn resolve_const2_property<'a>( } } -pub(super) fn resolve_continue2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_continue2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "continue" => { todo!("implement property 'continue' in fn `resolve_continue2_property()`") @@ -34,11 +34,11 @@ pub(super) fn resolve_continue2_property<'a>( } } -pub(super) fn resolve_dyn2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_dyn2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "dyn" => todo!("implement property 'dyn' in fn `resolve_dyn2_property()`"), _ => { @@ -49,11 +49,11 @@ pub(super) fn resolve_dyn2_property<'a>( } } -pub(super) fn resolve_if2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_if2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "if" => todo!("implement property 'if' in fn `resolve_if2_property()`"), _ => { @@ -64,11 +64,11 @@ pub(super) fn resolve_if2_property<'a>( } } -pub(super) fn resolve_mod2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_mod2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "mod" => todo!("implement property 'mod' in fn `resolve_mod2_property()`"), _ => { @@ -79,11 +79,11 @@ pub(super) fn resolve_mod2_property<'a>( } } -pub(super) fn resolve_self2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_self2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "self" => todo!("implement property 'self' in fn `resolve_self2_property()`"), _ => { @@ -94,11 +94,11 @@ pub(super) fn resolve_self2_property<'a>( } } -pub(super) fn resolve_type2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_type2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "type" => todo!("implement property 'type' in fn `resolve_type2_property()`"), _ => { @@ -109,11 +109,11 @@ pub(super) fn resolve_type2_property<'a>( } } -pub(super) fn resolve_unsafe2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_unsafe2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "unsafe" => { todo!("implement property 'unsafe' in fn `resolve_unsafe2_property()`") @@ -126,11 +126,11 @@ pub(super) fn resolve_unsafe2_property<'a>( } } -pub(super) fn resolve_where2_property<'a>( - contexts: ContextIterator<'a, Vertex>, +pub(super) fn resolve_where2_property<'a, V: AsVertex + 'a>( + contexts: ContextIterator<'a, V>, property_name: &str, _resolve_info: &ResolveInfo, -) -> ContextOutcomeIterator<'a, Vertex, FieldValue> { +) -> ContextOutcomeIterator<'a, V, FieldValue> { match property_name { "where" => todo!("implement property 'where' in fn `resolve_where2_property()`"), _ => { diff --git a/trustfall_wasm/src/adapter.rs b/trustfall_wasm/src/adapter.rs index eb89ade6..410ad98f 100644 --- a/trustfall_wasm/src/adapter.rs +++ b/trustfall_wasm/src/adapter.rs @@ -4,7 +4,7 @@ use gloo_utils::format::JsValueSerdeExt; use js_sys::try_iter; use trustfall_core::{ interpreter::{ - Adapter, ContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, + Adapter, AsVertex, ContextIterator, ContextOutcomeIterator, DataContext, ResolveEdgeInfo, ResolveInfo, VertexIterator, }, ir::{EdgeParameters as CoreEdgeParameters, FieldValue}, @@ -76,21 +76,18 @@ impl Iterator for JsVertexIterator { struct ContextAndValueIterator { inner: js_sys::Iterator, - registry: Rc>>>, + registry: Rc>>, next_item: u32, } impl ContextAndValueIterator { - fn new( - inner: js_sys::Iterator, - registry: Rc>>>, - ) -> Self { + fn new(inner: js_sys::Iterator, registry: Rc>>) -> Self { Self { inner, registry, next_item: 0 } } } impl Iterator for ContextAndValueIterator { - type Item = (DataContext, FieldValue); + type Item = (Opaque, FieldValue); fn next(&mut self) -> Option { let iter_next = @@ -118,7 +115,7 @@ impl Iterator for ContextAndValueIterator { struct ContextAndNeighborsIterator { inner: js_sys::Iterator, - registry: Rc>>>, + registry: Rc>>, next_item: u32, constants: Rc, } @@ -126,7 +123,7 @@ struct ContextAndNeighborsIterator { impl ContextAndNeighborsIterator { fn new( inner: js_sys::Iterator, - registry: Rc>>>, + registry: Rc>>, constants: Rc, ) -> Self { Self { inner, registry, next_item: 0, constants } @@ -134,7 +131,7 @@ impl ContextAndNeighborsIterator { } impl Iterator for ContextAndNeighborsIterator { - type Item = (DataContext, VertexIterator<'static, JsValue>); + type Item = (Opaque, VertexIterator<'static, JsValue>); fn next(&mut self) -> Option { let iter_next = @@ -168,21 +165,18 @@ impl Iterator for ContextAndNeighborsIterator { struct ContextAndBoolIterator { inner: js_sys::Iterator, - registry: Rc>>>, + registry: Rc>>, next_item: u32, } impl ContextAndBoolIterator { - fn new( - inner: js_sys::Iterator, - registry: Rc>>>, - ) -> Self { + fn new(inner: js_sys::Iterator, registry: Rc>>) -> Self { Self { inner, registry, next_item: 0 } } } impl Iterator for ContextAndBoolIterator { - type Item = (DataContext, bool); + type Item = (Opaque, bool); fn next(&mut self) -> Option { let iter_next = @@ -219,6 +213,34 @@ impl AdapterShim { } } +#[derive(Debug)] +pub(crate) struct Opaque { + data: *mut (), + pub(crate) vertex: Option, +} + +impl Opaque { + fn new + 'static>(ctx: DataContext) -> Self { + let vertex = ctx.active_vertex::().cloned(); + let boxed = Box::new(ctx); + let data = Box::into_raw(boxed) as *mut (); + + Self { data, vertex } + } + + /// Converts an `Opaque` into the `DataContext` it points to. + /// + /// # Safety + /// + /// When an `Opaque` is constructed, it does not store the value of the `V` generic parameter + /// it was constructed with. The caller of this function must ensure that the `V` parameter here + /// is the same type as the one used in the `Opaque::new()` call that constructed `self` here. + unsafe fn into_inner + 'static>(self) -> DataContext { + let boxed_ctx = unsafe { Box::from_raw(self.data as *mut DataContext) }; + *boxed_ctx + } +} + impl Adapter<'static> for AdapterShim { type Vertex = JsValue; @@ -234,29 +256,39 @@ impl Adapter<'static> for AdapterShim { Box::new(JsVertexIterator::new(js_iter.into_iter())) } - fn resolve_property( + fn resolve_property + 'static>( &self, - contexts: ContextIterator<'static, Self::Vertex>, + contexts: ContextIterator<'static, V>, type_name: &Arc, property_name: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, FieldValue> { - let ctx_iter = JsContextIterator::new(contexts); + ) -> ContextOutcomeIterator<'static, V, FieldValue> { + let opaques: Box> = Box::new(contexts.map(Opaque::new)); + + let ctx_iter = JsContextIterator::new(opaques); let registry = ctx_iter.registry.clone(); let js_iter = self.inner.resolve_property(ctx_iter, type_name.as_ref(), property_name.as_ref()); - Box::new(ContextAndValueIterator::new(js_iter, registry)) + Box::new(ContextAndValueIterator::new(js_iter, registry).map(|(opaque, value)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_property()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, value) + })) } - fn resolve_neighbors( + fn resolve_neighbors + 'static>( &self, - contexts: ContextIterator<'static, Self::Vertex>, + contexts: ContextIterator<'static, V>, type_name: &Arc, edge_name: &Arc, parameters: &CoreEdgeParameters, _resolve_info: &ResolveEdgeInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, VertexIterator<'static, Self::Vertex>> { - let ctx_iter = JsContextIterator::new(contexts); + ) -> ContextOutcomeIterator<'static, V, VertexIterator<'static, Self::Vertex>> { + let opaques: Box> = Box::new(contexts.map(Opaque::new)); + + let ctx_iter = JsContextIterator::new(opaques); let registry = ctx_iter.registry.clone(); let parameters: JsEdgeParameters = parameters.clone().into(); @@ -266,20 +298,36 @@ impl Adapter<'static> for AdapterShim { edge_name.as_ref(), parameters.into_js_dict(), ); - Box::new(ContextAndNeighborsIterator::new(js_iter, registry, self.constants.clone())) + Box::new(ContextAndNeighborsIterator::new(js_iter, registry, self.constants.clone()).map( + |(opaque, neighbors)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_neighbors()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, neighbors) + }, + )) } - fn resolve_coercion( + fn resolve_coercion + 'static>( &self, - contexts: ContextIterator<'static, Self::Vertex>, + contexts: ContextIterator<'static, V>, type_name: &Arc, coerce_to_type: &Arc, _resolve_info: &ResolveInfo, - ) -> ContextOutcomeIterator<'static, Self::Vertex, bool> { - let ctx_iter = JsContextIterator::new(contexts); + ) -> ContextOutcomeIterator<'static, V, bool> { + let opaques: Box> = Box::new(contexts.map(Opaque::new)); + + let ctx_iter = JsContextIterator::new(opaques); let registry = ctx_iter.registry.clone(); let js_iter = self.inner.resolve_coercion(ctx_iter, type_name.as_ref(), coerce_to_type.as_ref()); - Box::new(ContextAndBoolIterator::new(js_iter, registry)) + Box::new(ContextAndBoolIterator::new(js_iter, registry).map(|(opaque, value)| { + // SAFETY: This `Opaque` was constructed just a few lines ago + // in this `resolve_coercion()` call, so the `V` type must be the same. + let ctx = unsafe { opaque.into_inner() }; + + (ctx, value) + })) } } diff --git a/trustfall_wasm/src/shim.rs b/trustfall_wasm/src/shim.rs index 267db31d..b7670b69 100644 --- a/trustfall_wasm/src/shim.rs +++ b/trustfall_wasm/src/shim.rs @@ -4,10 +4,9 @@ use gloo_utils::format::JsValueSerdeExt; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -use trustfall_core::{ - interpreter::{DataContext, VertexIterator}, - ir::FieldValue, -}; +use trustfall_core::{interpreter::VertexIterator, ir::FieldValue}; + +use crate::adapter::Opaque; #[non_exhaustive] #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] @@ -123,8 +122,8 @@ impl JsStringConstants { #[wasm_bindgen] pub struct JsContextIterator { - iter: VertexIterator<'static, DataContext>, - pub(super) registry: Rc>>>, + iter: VertexIterator<'static, Opaque>, + pub(super) registry: Rc>>, next_item: u32, } @@ -156,7 +155,7 @@ impl ContextIteratorItem { #[wasm_bindgen] impl JsContextIterator { - pub(super) fn new(iter: VertexIterator<'static, DataContext>) -> Self { + pub(super) fn new(iter: VertexIterator<'static, Opaque>) -> Self { Self { iter, registry: Rc::from(RefCell::new(Default::default())), next_item: 0 } } @@ -166,7 +165,7 @@ impl JsContextIterator { if let Some(ctx) = next { let next_item = self.next_item; self.next_item = self.next_item.wrapping_add(1); - let current_vertex = ctx.active_vertex().cloned(); + let current_vertex = ctx.vertex.clone(); let existing = self.registry.borrow_mut().insert(next_item, ctx); assert!(existing.is_none(), "id {next_item} already inserted with value {existing:?}",);