From b74c64ebcd426a0872822b306ca8a201c3d4ac4b Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 09:54:19 +0000 Subject: [PATCH] Retarget typing and row consumers onto the registry entries --- .../document_node_derive.rs | 8 ++-- .../document/node_graph/node_properties.rs | 2 +- .../network_interface/resolved_types.rs | 12 +++--- node-graph/graph-craft/src/proto.rs | 42 +++++++++---------- node-graph/preprocessor/src/lib.rs | 10 ++--- tools/node-docs/src/page_category.rs | 7 ++-- tools/node-docs/src/page_node.rs | 6 +-- 7 files changed, 45 insertions(+), 42 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 78098dc950..f4f5b6fe98 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -29,7 +29,7 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap // fallback when deriving `call_argument` so it reflects the impls actually registered, which will usually be `Context`. let extended_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY; let node_registry = NODE_REGISTRY.lock().unwrap(); - let empty_implementations: Vec<(NodeConstructor, NodeIOTypes)> = Vec::new(); + let empty_implementations: Vec = Vec::new(); let context_type = concrete!(Context); for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { let identifier = DefinitionIdentifier::ProtoNode(id.clone()); @@ -48,12 +48,12 @@ pub(super) fn post_process_nodes(custom: Vec) -> HashMap let implementations = node_registry.get(id).unwrap_or(&empty_implementations); - let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let first_node_io = implementations.first().map(|entry| &entry.io).unwrap_or(const { &NodeIOTypes::empty() }); let call_arguments: Vec<&Type> = if !implementations.is_empty() { - implementations.iter().map(|(_, io)| &io.call_argument).collect() + implementations.iter().map(|entry| &entry.io.call_argument).collect() } else if let Some(impls) = extended_node_registry.get(id) { - impls.keys().map(|io| &io.call_argument).collect() + impls.iter().map(|entry| &entry.io.call_argument).collect() } else { Vec::new() }; diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 0350c6b421..9b400255f4 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -2360,7 +2360,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper return Vec::new(); }; - let mut input_types = implementations.keys().filter_map(|item| item.inputs.get(input_index)).collect::>(); + let mut input_types = implementations.iter().filter_map(|entry| entry.io.inputs.get(input_index)).collect::>(); input_types.sort_by_key(|ty| ty.type_name()); let input_type = input_types.first().cloned(); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index a821ebab79..a618475665 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -253,8 +253,9 @@ impl NodeNetworkInterface { }; let number_of_inputs = self.number_of_inputs(node_id, network_path); implementations - .keys() - .filter_map(|node_io| { + .iter() + .filter_map(|entry| { + let node_io = &entry.io; // Check if this NodeIOTypes implementation is valid for the other inputs let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| { let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path); @@ -293,8 +294,9 @@ impl NodeNetworkInterface { let valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path); implementations - .keys() - .filter_map(|node_io| { + .iter() + .filter_map(|entry| { + let node_io = &entry.io; if !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) { return None; } @@ -323,7 +325,7 @@ impl NodeNetworkInterface { log::error!("Protonode {render_node:?} not found in registry"); return Vec::new(); }; - implementations.keys().map(|types| types.inputs[1].clone()).collect() + implementations.iter().map(|entry| entry.io.inputs[1].clone()).collect() } } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 5bbfd55b0d..84807f77a0 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -545,6 +545,7 @@ pub enum GraphErrorType { }, NoImplementations, NoConstructor, + ConstructionFailed(String), /// The `inputs` represents a formatted list of input indices corresponding to their types. /// Each element in `error_inputs` represents a valid `NodeIOTypes` implementation. /// The inner Vec stores the inputs which need to be changed and what type each needs to be changed to. @@ -565,6 +566,7 @@ impl Debug for GraphErrorType { GraphErrorType::UnexpectedGenerics { index, inputs } => write!(f, "Generic inputs should not exist but found at {index}: {inputs:?}"), GraphErrorType::NoImplementations => write!(f, "No implementations found"), GraphErrorType::NoConstructor => write!(f, "No construct found for node"), + GraphErrorType::ConstructionFailed(error) => write!(f, "Construction failed: {error}"), GraphErrorType::InvalidImplementations { inputs, error_inputs } => { let format_error = |(index, (found, expected)): &(usize, (Type, Type))| { let index = index + 1; @@ -631,14 +633,14 @@ pub type GraphErrors = Vec; /// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. #[derive(Default, Clone, dyn_any::DynAny)] pub struct TypingContext { - lookup: Cow<'static, HashMap>>, + lookup: Cow<'static, HashMap>>, inferred: HashMap, - constructor: HashMap, + constructor: HashMap, } impl TypingContext { /// Creates a new `TypingContext` with the given lookup table. - pub fn new(lookup: &'static HashMap>) -> Self { + pub fn new(lookup: &'static HashMap>) -> Self { Self { lookup: Cow::Borrowed(lookup), ..Default::default() @@ -662,7 +664,7 @@ impl TypingContext { } /// Returns the node constructor for a given node id. - pub fn constructor(&self, node_id: NodeId) -> Option { + pub fn constructor(&self, node_id: NodeId) -> Option { self.constructor.get(&node_id).copied() } @@ -682,7 +684,7 @@ impl TypingContext { // If the node has a value input we can infer the return type from it ConstructionArgs::Value(ref v) => { // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); + let types = NodeIOTypes::new(concrete!(Context), v.ty(), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } @@ -702,6 +704,7 @@ impl TypingContext { // Get the node input type from the proto node declaration let call_argument = &node.call_argument; let impls = self.lookup.get(&node.identifier).ok_or_else(|| vec![GraphError::new(node, GraphErrorType::NoImplementations)])?; + let candidates: Vec<(NodeIOTypes, NodeConstructor)> = impls.iter().map(|entry| (entry.io.clone(), entry.constructor)).collect(); if let Some(index) = inputs.iter().position(|p| { matches!(p, @@ -716,8 +719,6 @@ impl TypingContext { match (from, to) { // Direct comparison of two concrete types. (Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2, - // Check inner type for futures - (Type::Future(type1), Type::Future(type2)) => valid_type(type1, type2), // Direct comparison of two function types. // Note: in the presence of subtyping, functions are considered on a "greater than or equal to" basis of its function type's generality. // That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature. @@ -740,25 +741,24 @@ impl TypingContext { } // List of all implementations that match the input types - let valid_output_types = impls - .keys() - .filter(|node_io| valid_type(&node_io.call_argument, call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2))) + let valid_output_types = candidates + .iter() + .filter(|(node_io, _)| valid_type(&node_io.call_argument, call_argument) && inputs.iter().zip(node_io.inputs.iter()).all(|(p1, p2)| valid_type(p1, p2))) .collect::>(); // Attempt to substitute generic types with concrete types and save the list of results let substitution_results = valid_output_types .iter() - .map(|node_io| { + .map(|(node_io, constructor)| { let generics_lookup: Result, _> = collect_generics(node_io) .iter() .map(|generic| check_generic(node_io, call_argument, &inputs, generic).map(|x| (generic.to_string(), x))) .collect(); generics_lookup.map(|generics_lookup| { - let orig_node_io = (*node_io).clone(); - let mut new_node_io = orig_node_io.clone(); + let mut new_node_io = node_io.clone(); replace_generics(&mut new_node_io, &generics_lookup); - (new_node_io, orig_node_io) + (new_node_io, *constructor) }) }) .collect::>(); @@ -771,7 +771,7 @@ impl TypingContext { let convert_node_index_offset = node.original_location.auto_convert_index.unwrap_or(0); let mut best_errors = usize::MAX; let mut error_inputs = Vec::new(); - for node_io in impls.keys() { + for (node_io, _) in &candidates { // For errors on Convert nodes, offset the input index so it correctly corresponds to the node it is connected to. let current_errors = [call_argument] .into_iter() @@ -806,36 +806,36 @@ impl TypingContext { .join("\n"); Err(vec![GraphError::new(node, GraphErrorType::InvalidImplementations { inputs, error_inputs })]) } - [(node_io, org_nio)] => { + [(node_io, constructor)] => { let node_io = node_io.clone(); // Save the inferred type self.inferred.insert(node_id, node_io.clone()); - self.constructor.insert(node_id, impls[org_nio]); + self.constructor.insert(node_id, *constructor); Ok(node_io) } // If two types are available and one of them accepts () an input, always choose that one [first, second] => { if first.0.call_argument != second.0.call_argument { - for (node_io, orig_nio) in [first, second] { + for (node_io, constructor) in [first, second] { if node_io.call_argument != concrete!(()) { continue; } // Save the inferred type self.inferred.insert(node_id, node_io.clone()); - self.constructor.insert(node_id, impls[orig_nio]); + self.constructor.insert(node_id, *constructor); return Ok(node_io.clone()); } } let inputs = [call_argument].into_iter().chain(&inputs).map(ToString::to_string).collect::>().join(", "); - let valid = valid_output_types.into_iter().cloned().collect(); + let valid = valid_output_types.into_iter().map(|(node_io, _)| node_io.clone()).collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } _ => { let inputs = [call_argument].into_iter().chain(&inputs).map(ToString::to_string).collect::>().join(", "); - let valid = valid_output_types.into_iter().cloned().collect(); + let valid = valid_output_types.into_iter().map(|(node_io, _)| node_io.clone()).collect(); Err(vec![GraphError::new(node, GraphErrorType::MultipleImplementations { inputs, valid })]) } } diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 4e543a7b7a..5217e51da4 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -118,11 +118,11 @@ impl Preprocessor { let NodeMetadata { fields, memoize, inject_scope, .. } = metadata; let Some(implementations) = node_registry.get(&id) else { continue }; - let valid_call_args: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); - let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let valid_call_args: HashSet<_> = implementations.iter().map(|entry| entry.io.call_argument.clone()).collect(); + let first_node_io = implementations.first().map(|entry| &entry.io).unwrap_or(const { &NodeIOTypes::empty() }); let mut node_io_types = vec![HashSet::new(); fields.len()]; - for (_, node_io) in implementations.iter() { - for (i, ty) in node_io.inputs.iter().enumerate() { + for entry in implementations.iter() { + for (i, ty) in entry.io.inputs.iter().enumerate() { node_io_types[i].insert(ty.clone()); } } @@ -238,7 +238,7 @@ impl Preprocessor { // If `inject_scope` is requested, prepare the proto node template and type info needed if *inject_scope && let Some(implementations) = node_registry.get(&id) - && let Some((_, node_io)) = implementations.first() + && let Some(node_io) = implementations.first().map(|entry| &entry.io) { let template = DocumentNode { inputs: node_inputs(fields, node_io), diff --git a/tools/node-docs/src/page_category.rs b/tools/node-docs/src/page_category.rs index e16a50723d..ae6dda791c 100644 --- a/tools/node-docs/src/page_category.rs +++ b/tools/node-docs/src/page_category.rs @@ -76,15 +76,16 @@ fn write_nodes_table_rows(page: &mut std::fs::File, nodes: &[(&core_types::Proto let implementations = node_registry.get(id)?; let valid_primary_inputs_to_outputs = implementations .iter() - .map(|(_, node_io)| { - let input = node_io + .map(|entry| { + let input = entry + .io .inputs .first() .map(|ty| ty.nested_type()) .filter(|&ty| ty != &concrete!(())) .map(ToString::to_string) .unwrap_or_default(); - let output = node_io.return_value.nested_type().to_string(); + let output = entry.io.return_value.nested_type().to_string(); format!("`{input} → {output}`") }) .collect::>(); diff --git a/tools/node-docs/src/page_node.rs b/tools/node-docs/src/page_node.rs index e4a9962020..df4a868a80 100644 --- a/tools/node-docs/src/page_node.rs +++ b/tools/node-docs/src/page_node.rs @@ -23,8 +23,8 @@ pub fn write_node_page(index: usize, id: &core_types::ProtoNodeIdentifier, metad // Input types let mut valid_input_types = vec![Vec::new(); metadata.fields.len()]; - for (_, node_io) in implementations.iter() { - for (i, ty) in node_io.inputs.iter().enumerate() { + for entry in implementations.iter() { + for (i, ty) in entry.io.inputs.iter().enumerate() { valid_input_types[i].push(ty.nested_type().clone()); } } @@ -35,7 +35,7 @@ pub fn write_node_page(index: usize, id: &core_types::ProtoNodeIdentifier, metad } // Primary output types - let valid_primary_outputs = implementations.iter().map(|(_, node_io)| node_io.return_value.nested_type().clone()).collect::>(); + let valid_primary_outputs = implementations.iter().map(|entry| entry.io.return_value.nested_type().clone()).collect::>(); // Write sections to the file write_frontmatter(&mut page, metadata, index + 1);