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 Keavon Chambers
parent a64c856ec4
commit 5dab7de68d
25 changed files with 1365 additions and 1008 deletions

View File

@@ -6,28 +6,44 @@ use dyn_any::StaticType;
use graph_craft::document::value::UpcastNode;
use graph_craft::document::NodeId;
use graph_craft::executor::Executor;
use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graph_craft::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, TypingContext};
use graphene_std::any::{Any, TypeErasedPinned, TypeErasedPinnedRef};
use crate::node_registry::constrcut_node;
use crate::node_registry;
#[derive(Default, Debug, Clone)]
#[derive(Debug, Clone)]
pub struct DynamicExecutor {
output: NodeId,
tree: BorrowTree,
typing_context: TypingContext,
}
impl Default for DynamicExecutor {
fn default() -> Self {
Self {
output: Default::default(),
tree: Default::default(),
typing_context: TypingContext::new(&node_registry::NODE_REGISTRY),
}
}
}
impl DynamicExecutor {
pub fn new(proto_network: ProtoNetwork) -> Self {
pub fn new(proto_network: ProtoNetwork) -> Result<Self, String> {
let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY);
typing_context.update(&proto_network)?;
let output = proto_network.output;
let tree = BorrowTree::new(proto_network);
Self { tree, output }
let tree = BorrowTree::new(proto_network, &typing_context)?;
Ok(Self { tree, output, typing_context })
}
pub fn update(&mut self, proto_network: ProtoNetwork) {
pub fn update(&mut self, proto_network: ProtoNetwork) -> Result<(), String> {
self.output = proto_network.output;
self.typing_context.update(&proto_network)?;
trace!("setting output to {}", self.output);
self.tree.update(proto_network);
self.tree.update(proto_network, &self.typing_context)?;
Ok(())
}
}
@@ -38,7 +54,7 @@ impl Executor for DynamicExecutor {
}
pub struct NodeContainer<'n> {
node: TypeErasedPinned<'n>,
pub node: TypeErasedPinned<'n>,
// the dependencies are only kept to ensure that the nodes are not dropped while still in use
_dependencies: Vec<Arc<NodeContainer<'static>>>,
}
@@ -50,6 +66,10 @@ impl<'a> core::fmt::Debug for NodeContainer<'a> {
}
impl<'a> NodeContainer<'a> {
pub fn new(node: TypeErasedPinned<'a>, _dependencies: Vec<Arc<NodeContainer<'static>>>) -> Self {
Self { node, _dependencies }
}
/// Return a static reference to the TypeErasedNode
/// # Safety
/// This is unsafe because the returned reference is only valid as long as the NodeContainer is alive
@@ -58,7 +78,7 @@ impl<'a> NodeContainer<'a> {
}
}
impl NodeContainer<'static> {
unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> {
pub unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> {
let s = &*(self as *const Self);
*(&s.node.as_ref() as *const TypeErasedPinnedRef<'static>)
}
@@ -70,24 +90,24 @@ pub struct BorrowTree {
}
impl BorrowTree {
pub fn new(proto_network: ProtoNetwork) -> Self {
pub fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<Self, String> {
let mut nodes = BorrowTree::default();
for (id, node) in proto_network.nodes {
nodes.push_node(id, node)
nodes.push_node(id, node, typing_context)?
}
nodes
Ok(nodes)
}
/// Pushes new nodes into the tree and return orphaned nodes
pub fn update(&mut self, proto_network: ProtoNetwork) -> Vec<NodeId> {
pub fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<Vec<NodeId>, String> {
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();
for (id, node) in proto_network.nodes {
if !self.nodes.contains_key(&id) {
self.push_node(id, node);
self.push_node(id, node, typing_context)?;
old_nodes.remove(&id);
}
}
old_nodes.into_iter().collect()
Ok(old_nodes.into_iter().collect())
}
fn node_refs(&self, nodes: &[NodeId]) -> Vec<TypeErasedPinnedRef<'static>> {
@@ -120,16 +140,8 @@ impl BorrowTree {
self.nodes.remove(&id);
}
pub fn push_node(&mut self, id: NodeId, proto_node: ProtoNode) {
let ProtoNode { input, construction_args, identifier } = proto_node;
assert!(
!matches!(&input, &ProtoNodeInput::Node(_)),
"Only nodes without inputs are supported. Any inputs should already be resolved by placing ComposeNodes {:?}, {:?}, {:?}",
identifier,
construction_args,
input,
);
pub fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), String> {
let ProtoNode { construction_args, identifier, .. } = proto_node;
match construction_args {
ConstructionArgs::Value(value) => {
@@ -141,7 +153,8 @@ impl BorrowTree {
}
ConstructionArgs::Nodes(ids) => {
let construction_nodes = self.node_refs(&ids);
let node = constrcut_node(identifier, construction_nodes);
let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?;
let node = constructor(construction_nodes);
let node = NodeContainer {
node,
_dependencies: self.node_deps(&ids),
@@ -149,7 +162,8 @@ impl BorrowTree {
let node = unsafe { node.erase_lifetime() };
self.store_node(Arc::new(node), id);
}
}
};
Ok(())
}
}
@@ -163,7 +177,7 @@ mod test {
fn push_node() {
let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32)));
tree.push_node(0, val_1_protonode);
tree.push_node(0, val_1_protonode, &TypingContext::default()).unwrap();
let _node = tree.get(0).unwrap();
assert_eq!(tree.eval(0, ()), Some(2u32));
}