Add type checking to the node graph (#1025)

* Implement type inference

Add type hints to node trait

Add type annotation infrastructure

Refactor type ascription infrastructure

Run cargo fix

Insert infer types stub

Remove types from node identifier

* Implement covariance

* Disable rejection of generic inputs + parameters

* Fix lints

* Extend type checking to cover Network inputs

* Implement generic specialization

* Relax covariance rules

* Fix type annotations for TypErasedComposeNode

* Fix type checking errors

* Keep connection information during node resolution
* Fix TypeDescriptor PartialEq implementation

* Apply review suggestions

* Add documentation to type inference

* Add Imaginate node to document node types

* Fix whitespace in macros

* Add types to imaginate node

* Fix type declaration for imaginate node + add console logging

* Use fully qualified type names as fallback during comparison

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2023-02-15 23:31:30 +01:00
committed by GitHub
parent 76be1f8515
commit 6f371d784d
25 changed files with 1365 additions and 1008 deletions

View File

@@ -1,13 +1,15 @@
use crate::document::value::TaggedValue;
use crate::generic;
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput, Type};
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::{NodeIdentifier, Type};
use dyn_any::{DynAny, StaticType};
use glam::IVec2;
use graphene_core::TypeDescriptor;
use rand_chacha::{
rand_core::{RngCore, SeedableRng},
ChaCha20Rng,
};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
@@ -59,7 +61,7 @@ impl DocumentNode {
.inputs
.iter()
.enumerate()
.filter(|(_, input)| matches!(input, NodeInput::Network))
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
.nth(offset)
.expect("no network input");
@@ -80,9 +82,9 @@ impl DocumentNode {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
(ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
};
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter");
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network(_))), "recieved non resolved parameter");
assert!(
!self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })),
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
@@ -129,12 +131,12 @@ impl DocumentNode {
}
}
#[derive(Clone, Debug, specta::Type)]
#[derive(Debug, Clone, PartialEq, Hash, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeInput {
Node { node_id: NodeId, output_index: usize },
Value { tagged_value: value::TaggedValue, exposed: bool },
Network,
Network(Type),
}
impl NodeInput {
@@ -153,17 +155,14 @@ impl NodeInput {
match self {
NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Network => false,
NodeInput::Network(_) => false,
}
}
}
impl PartialEq for NodeInput {
fn eq(&self, other: &Self) -> bool {
match (&self, &other) {
(Self::Node { node_id: n0, output_index: o0 }, Self::Node { node_id: n1, output_index: o1 }) => n0 == n1 && o0 == o1,
(Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
pub fn ty(&self) -> Type {
match self {
NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"),
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
NodeInput::Network(ty) => ty.clone(),
}
}
}
@@ -357,7 +356,7 @@ impl NodeNetwork {
.unwrap_or_else(|| panic!("The node which was supposed to be flattened does not exist in the network, id {} network {:#?}", node, self));
if self.disabled.contains(&id) {
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
node.inputs.drain(1..);
self.nodes.insert(id, node);
return;
@@ -394,7 +393,7 @@ impl NodeNetwork {
let value_node = DocumentNode {
name,
inputs: vec![NodeInput::Value { tagged_value, exposed }],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
metadata: DocumentNodeMetadata::default(),
};
assert!(!self.nodes.contains_key(&new_id));
@@ -402,7 +401,7 @@ impl NodeNetwork {
let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(new_id, 0, *offset);
}
NodeInput::Network => {
NodeInput::Network(_) => {
*network_offsets.get_mut(network_input).unwrap() += 1;
if let Some(index) = self.inputs.iter().position(|i| *i == id) {
self.inputs[index] = *network_input;
@@ -410,7 +409,7 @@ impl NodeNetwork {
}
}
}
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
node.implementation = DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into());
node.inputs = inner_network
.outputs
.iter()
@@ -419,6 +418,7 @@ impl NodeNetwork {
output_index: node_output_index,
})
.collect();
for node_id in new_nodes {
self.flatten_with_fns(node_id, map_ids, gen_id);
}
@@ -456,8 +456,8 @@ impl NodeNetwork {
0,
DocumentNode {
name: "Input".into(),
inputs: vec![NodeInput::Network],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
inputs: vec![NodeInput::Network(concrete!(u32))],
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
metadata: DocumentNodeMetadata { position: (8, 4).into() },
},
),
@@ -466,7 +466,7 @@ impl NodeNetwork {
DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::node(output_node_id, 0)],
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
},
),
@@ -553,7 +553,8 @@ impl NodeNetwork {
#[cfg(test)]
mod test {
use super::*;
use crate::proto::{ConstructionArgs, NodeIdentifier, ProtoNetwork, ProtoNode, ProtoNodeInput};
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::NodeIdentifier;
fn gen_node_id() -> NodeId {
static mut NODE_ID: NodeId = 3;
@@ -572,9 +573,9 @@ mod test {
0,
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::Network],
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
},
),
(
@@ -583,7 +584,7 @@ mod test {
name: "Add".into(),
inputs: vec![NodeInput::node(0, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
},
),
]
@@ -605,9 +606,9 @@ mod test {
1,
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::Network],
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(u32))],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
},
),
(
@@ -616,7 +617,7 @@ mod test {
name: "Add".into(),
inputs: vec![NodeInput::node(1, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
},
),
]
@@ -637,7 +638,7 @@ mod test {
DocumentNode {
name: "Inc".into(),
inputs: vec![
NodeInput::Network,
NodeInput::Network(concrete!(u32)),
NodeInput::Value {
tagged_value: value::TaggedValue::U32(2),
exposed: false,
@@ -663,15 +664,15 @@ mod test {
fn resolve_proto_node_add() {
let document_node = DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::node(0, 0)],
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(0, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
};
let proto_node = document_node.resolve_proto_node();
let reference = ProtoNode {
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]),
input: ProtoNodeInput::Network,
identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![0]),
};
assert_eq!(proto_node, reference);
@@ -686,7 +687,7 @@ mod test {
(
1,
ProtoNode {
identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]),
identifier: "graphene_core::ops::IdNode".into(),
input: ProtoNodeInput::Node(11),
construction_args: ConstructionArgs::Nodes(vec![]),
},
@@ -694,15 +695,15 @@ mod test {
(
10,
ProtoNode {
identifier: NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")]),
input: ProtoNodeInput::Network,
identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![14]),
},
),
(
11,
ProtoNode {
identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")]),
identifier: "graphene_core::ops::AddNode".into(),
input: ProtoNodeInput::Node(10),
construction_args: ConstructionArgs::Nodes(vec![]),
},
@@ -731,16 +732,16 @@ mod test {
name: "Inc".into(),
inputs: vec![NodeInput::node(11, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()),
},
),
(
10,
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network, NodeInput::node(14, 0)],
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::node(14, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::structural::ConsNode".into()),
},
),
(
@@ -752,7 +753,7 @@ mod test {
exposed: false,
}],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::value::ValueNode".into()),
},
),
(
@@ -761,7 +762,7 @@ mod test {
name: "Add".into(),
inputs: vec![NodeInput::node(10, 0)],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
implementation: DocumentNodeImplementation::Unresolved("graphene_core::ops::AddNode".into()),
},
),
]
@@ -780,18 +781,18 @@ mod test {
1,
DocumentNode {
name: "Identity 1".into(),
inputs: vec![NodeInput::Network],
inputs: vec![NodeInput::Network(concrete!(u32))],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
},
),
(
2,
DocumentNode {
name: "Identity 2".into(),
inputs: vec![NodeInput::Network],
inputs: vec![NodeInput::Network(concrete!(u32))],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
},
),
]
@@ -821,7 +822,7 @@ mod test {
name: "Result".into(),
inputs: vec![result_node_input],
metadata: DocumentNodeMetadata::default(),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode")),
},
),
]

View File

@@ -3,7 +3,7 @@ use dyn_any::{DynAny, Upcast};
use dyn_clone::DynClone;
pub use glam::{DAffine2, DVec2};
use graphene_core::raster::LuminanceCalculation;
use graphene_core::Node;
use graphene_core::{Node, Type};
use std::hash::Hash;
pub use std::sync::Arc;
@@ -142,6 +142,32 @@ impl<'a> TaggedValue {
TaggedValue::LayerPath(x) => Box::new(x),
}
}
pub fn ty(&self) -> Type {
use graphene_core::TypeDescriptor;
use std::borrow::Cow;
match self {
TaggedValue::None => concrete!(()),
TaggedValue::String(_) => concrete!(String),
TaggedValue::U32(_) => concrete!(u32),
TaggedValue::F32(_) => concrete!(f32),
TaggedValue::F64(_) => concrete!(f64),
TaggedValue::Bool(_) => concrete!(bool),
TaggedValue::DVec2(_) => concrete!(DVec2),
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image),
TaggedValue::RcImage(_) => concrete!(Option<Arc<graphene_core::raster::Image>>),
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
TaggedValue::Subpath(_) => concrete!(graphene_core::vector::subpath::Subpath),
TaggedValue::RcSubpath(_) => concrete!(Arc<graphene_core::vector::subpath::Subpath>),
TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod),
TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill),
TaggedValue::ImaginateStatus(_) => concrete!(ImaginateStatus),
TaggedValue::LayerPath(_) => concrete!(Option<Vec<u64>>),
TaggedValue::DAffine2(_) => concrete!(DAffine2),
TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation),
}
}
}
pub struct UpcastNode {

View File

@@ -1,6 +1,10 @@
#[macro_use]
extern crate log;
#[macro_use]
extern crate graphene_core;
pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor};
pub mod document;
pub mod proto;

View File

@@ -4,87 +4,16 @@ use std::hash::Hash;
use crate::document::value;
use crate::document::NodeId;
use dyn_any::DynAny;
use graphene_core::*;
use std::pin::Pin;
#[macro_export]
macro_rules! concrete {
($type:expr) => {
Type::Concrete(std::borrow::Cow::Borrowed($type))
};
}
#[macro_export]
macro_rules! generic {
($type:expr) => {
Type::Generic(std::borrow::Cow::Borrowed($type))
};
}
pub type Any<'n> = Box<dyn DynAny<'n> + 'n>;
pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync;
pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync)>;
pub type TypeErasedPinned<'n> = Pin<Box<dyn for<'i> NodeIO<'i, Any<'i>, Output = Any<'i>> + 'n + Send + Sync>>;
#[derive(Clone, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeIdentifier {
pub name: std::borrow::Cow<'static, str>,
pub types: std::borrow::Cow<'static, [Type]>,
}
impl NodeIdentifier {
pub fn fully_qualified_name(&self) -> String {
let mut name = String::new();
name.push_str(self.name.as_ref());
name.push('<');
for t in self.types.as_ref() {
name.push_str(t.to_string().as_str());
name.push_str(", ");
}
name.pop();
name.pop();
name.push('>');
name
}
}
#[derive(Clone, Debug, PartialEq, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Type {
Generic(std::borrow::Cow<'static, str>),
Concrete(std::borrow::Cow<'static, str>),
}
impl From<&'static str> for Type {
fn from(s: &'static str) -> Self {
Type::Concrete(std::borrow::Cow::Borrowed(s))
}
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Generic(name) => write!(f, "{}", name),
Type::Concrete(name) => write!(f, "{}", name),
}
}
}
impl Type {
pub const fn from_str(concrete: &'static str) -> Self {
Type::Concrete(std::borrow::Cow::Borrowed(concrete))
}
}
impl From<&'static str> for NodeIdentifier {
fn from(s: &'static str) -> Self {
NodeIdentifier {
name: std::borrow::Cow::Borrowed(s),
types: std::borrow::Cow::Borrowed(&[]),
}
}
}
impl NodeIdentifier {
pub const fn new(name: &'static str, types: &'static [Type]) -> Self {
NodeIdentifier {
name: std::borrow::Cow::Borrowed(name),
types: std::borrow::Cow::Borrowed(types),
}
}
}
pub type NodeConstructor = for<'a> fn(Vec<TypeErasedPinnedRef<'static>>) -> TypeErasedPinned<'static>;
#[derive(Debug, Default, PartialEq)]
pub struct ProtoNetwork {
@@ -140,11 +69,10 @@ pub struct ProtoNode {
pub identifier: NodeIdentifier,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ProtoNodeInput {
None,
#[default]
Network,
Network(Type),
Node(NodeId),
}
@@ -161,11 +89,14 @@ impl ProtoNode {
pub fn stable_node_id(&self) -> Option<NodeId> {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.identifier.fully_qualified_name().hash(&mut hasher);
self.identifier.name.hash(&mut hasher);
self.construction_args.hash(&mut hasher);
match self.input {
ProtoNodeInput::None => "none".hash(&mut hasher),
ProtoNodeInput::Network => "network".hash(&mut hasher),
ProtoNodeInput::Network(ref ty) => {
"network".hash(&mut hasher);
ty.hash(&mut hasher);
}
ProtoNodeInput::Node(id) => id.hash(&mut hasher),
};
Some(hasher.finish() as NodeId)
@@ -173,7 +104,7 @@ impl ProtoNode {
pub fn value(value: ConstructionArgs) -> Self {
Self {
identifier: NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic(Cow::Borrowed("T"))]),
identifier: NodeIdentifier::new("graphene_core::value::ValueNode"),
construction_args: value,
input: ProtoNodeInput::None,
}
@@ -260,20 +191,22 @@ impl ProtoNetwork {
}
pub fn resolve_inputs(&mut self) {
while !self.resolve_inputs_impl() {}
let mut resolved = HashSet::new();
while !self.resolve_inputs_impl(&mut resolved) {}
}
fn resolve_inputs_impl(&mut self) -> bool {
fn resolve_inputs_impl(&mut self, resolved: &mut HashSet<NodeId>) -> bool {
self.reorder_ids();
let mut lookup = self.nodes.iter().map(|(id, _)| (*id, *id)).collect::<HashMap<_, _>>();
let compose_node_id = self.nodes.len() as NodeId;
let inputs = self.nodes.iter().map(|(_, node)| node.input).collect::<Vec<_>>();
let inputs = self.nodes.iter().map(|(_, node)| node.input.clone()).collect::<Vec<_>>();
if let Some((input_node, id, input)) = self.nodes.iter_mut().find_map(|(id, node)| {
let resolved_lookup = resolved.clone();
if let Some((input_node, id, input)) = self.nodes.iter_mut().filter(|(id, _)| !resolved_lookup.contains(id)).find_map(|(id, node)| {
if let ProtoNodeInput::Node(input_node) = node.input {
node.input = ProtoNodeInput::None;
resolved.insert(*id);
let pre_node_input = inputs.get(input_node as usize).expect("input node should exist");
Some((input_node, *id, *pre_node_input))
Some((input_node, *id, pre_node_input.clone()))
} else {
None
}
@@ -283,7 +216,7 @@ impl ProtoNetwork {
self.nodes.push((
compose_node_id,
ProtoNode {
identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>", &[generic!("T"), generic!("U")]),
identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
construction_args: ConstructionArgs::Nodes(vec![input_node, id]),
input,
},
@@ -373,6 +306,172 @@ impl ProtoNetwork {
}
}
/// The `TypingContext` is used to store the types of the nodes indexed by their stable node id.
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct TypingContext {
lookup: Cow<'static, HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>>,
inferred: HashMap<NodeId, NodeIOTypes>,
constructor: HashMap<NodeId, NodeConstructor>,
}
impl TypingContext {
/// Creates a new `TypingContext` with the given lookup table.
pub fn new(lookup: &'static HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>>) -> Self {
Self {
lookup: Cow::Borrowed(lookup),
..Default::default()
}
}
/// Updates the `TypingContext` wtih a given proto network. This will infer the types of the nodes
/// and store them in the `inferred` field. The proto network has to be topologically sorted
/// and contain fully resolved stable node ids.
pub fn update(&mut self, network: &ProtoNetwork) -> Result<(), String> {
for (id, node) in network.nodes.iter() {
self.infer(*id, node)?;
}
Ok(())
}
/// Returns the node constructor for a given node id.
pub fn constructor(&self, node_id: NodeId) -> Option<NodeConstructor> {
self.constructor.get(&node_id).copied()
}
/// Returns the inferred types for a given node id.
pub fn infer(&mut self, node_id: NodeId, node: &ProtoNode) -> Result<NodeIOTypes, String> {
let identifier = node.identifier.name.clone();
// Return the inferred type if it is already known
if let Some(infered) = self.inferred.get(&node_id) {
return Ok(infered.clone());
}
let parameters = match node.construction_args {
// If the node has a value parameter we can infer the return type from it
ConstructionArgs::Value(ref v) => {
assert!(matches!(node.input, ProtoNodeInput::None));
let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![]);
self.inferred.insert(node_id, types.clone());
return Ok(types);
}
// If the node has nodes as parameters we can infer the types from the node outputs
ConstructionArgs::Nodes(ref nodes) => nodes
.iter()
.map(|id| {
self.inferred
.get(id)
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
.map(|node| node.output.clone())
})
.collect::<Result<Vec<Type>, String>>()?,
};
// Get the node input type from the proto node declaration
let input = match node.input {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::Network(ref ty) => ty.clone(),
ProtoNodeInput::Node(id) => {
let input = self
.inferred
.get(&id)
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))?;
input.output.clone()
}
};
let impls = self.lookup.get(&node.identifier).ok_or(format!("No implementations found for {:?}", node.identifier))?;
if matches!(input, Type::Generic(_)) {
return Err(format!("Generic types are not supported as inputs yet {:?} occured in {:?}", &input, node.identifier));
}
if parameters.iter().any(|p| matches!(p, Type::Generic(_))) {
return Err(format!("Generic types are not supported in parameters: {:?} occured in {:?}", parameters, node.identifier));
}
let covariant = |output, input| match (&output, &input) {
(Type::Concrete(t1), Type::Concrete(t2)) => t1 == t2,
(Type::Concrete(_), Type::Generic(_)) => true,
// TODO: relax this requirement when allowing generic types as inputs
(Type::Generic(_), _) => false,
};
// List of all implementations that match the input and parameter types
let valid_output_types = impls
.keys()
.filter(|node_io| covariant(input.clone(), node_io.input.clone()) && parameters.iter().zip(node_io.parameters.iter()).all(|(p1, p2)| covariant(p1.clone(), p2.clone())))
.collect::<Vec<_>>();
// Attempt to substitute generic types with concrete types and save the list of results
let substitution_results = valid_output_types
.iter()
.map(|node_io| {
collect_generics(node_io)
.iter()
.try_for_each(|generic| check_generic(node_io, &input, &parameters, generic).map(|_| ()))
.map(|_| {
if let Type::Generic(out) = &node_io.output {
((*node_io).clone(), check_generic(node_io, &input, &parameters, out).unwrap())
} else {
((*node_io).clone(), node_io.output.clone())
}
})
})
.collect::<Vec<_>>();
// Collect all substitutions that are valid
let valid_impls = substitution_results.iter().filter_map(|result| result.as_ref().ok()).collect::<Vec<_>>();
match valid_impls.as_slice() {
[] => {
dbg!(&self.inferred);
Err(format!(
"No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}",
impls,
))
}
[(org_nio, output)] => {
let node_io = NodeIOTypes::new(input, (*output).clone(), parameters);
// Save the inferred type
self.inferred.insert(node_id, node_io.clone());
self.constructor.insert(node_id, impls[org_nio]);
Ok(node_io)
}
_ => Err(format!(
"Multiple implementations found for {identifier} with input {input:?} and parameters {parameters:?} (valid types: {valid_output_types:?}"
)),
}
}
}
/// Returns a list of all generic types used in the node
fn collect_generics(types: &NodeIOTypes) -> Vec<Cow<'static, str>> {
let inputs = [&types.input].into_iter().chain(types.parameters.iter());
let mut generics = inputs
.filter_map(|t| match t {
Type::Generic(out) => Some(out.clone()),
_ => None,
})
.collect::<Vec<_>>();
if let Type::Generic(out) = &types.output {
generics.push(out.clone());
}
generics.dedup();
generics
}
/// Checks if a generic type can be substituted with a concrete type and returns the concrete type
fn check_generic(types: &NodeIOTypes, input: &Type, parameters: &[Type], generic: &str) -> Result<Type, String> {
let inputs = [(&types.input, input)].into_iter().chain(types.parameters.iter().zip(parameters.iter()));
let mut concrete_inputs = inputs.filter(|(ni, _)| matches!(ni, Type::Generic(input) if generic == input));
let (_, out_ty) = concrete_inputs
.next()
.ok_or_else(|| format!("Generic output type {generic} is not dependent on input {input:?} or parameters {parameters:?}",))?;
if concrete_inputs.any(|(_, ty)| ty != out_ty) {
return Err(format!("Generic output type {generic} is dependent on multiple inputs or parameters",));
}
Ok(out_ty.clone())
}
#[cfg(test)]
mod test {
use super::*;
@@ -436,12 +535,12 @@ mod test {
assert_eq!(
ids,
vec![
17495035641492238530,
14931179783740213471,
2268573767208263092,
14616574692620381527,
12110007198416821768,
11185814750012198757
15907139529964845467,
17186311536944112733,
1674503539363691855,
10408773954839245246,
1677533587730447846,
6826908746727711035
]
);
}
@@ -471,7 +570,7 @@ mod test {
10,
ProtoNode {
identifier: "cons".into(),
input: ProtoNodeInput::Network,
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![14]),
},
),