use derive_new::new;
use onnx_ir_derive::NodeBuilder;
use crate::ir::{ArgType, Argument, Node, RawNode, RuntimeInputRef, TensorDataExt, TensorType};
use crate::processor::{
InputPreferences, InputSpec, NodeProcessor, NodeSpec, OutputPreferences, OutputSpec,
ProcessError,
};
#[derive(Debug, Clone, new)]
pub struct ReshapeConfig {
pub shape: ReshapeInput,
}
#[derive(Debug, Clone)]
pub enum ReshapeInput {
Static(Vec<i64>),
Runtime(RuntimeInputRef),
}
impl Default for ReshapeInput {
fn default() -> Self {
Self::Static(Vec::new())
}
}
#[derive(Debug, Clone, NodeBuilder)]
pub struct ReshapeNode {
pub name: String,
pub inputs: Vec<Argument>,
pub outputs: Vec<Argument>,
pub config: ReshapeConfig,
}
struct InputInfo {
dtype: crate::ir::DType,
is_shape: bool,
shape_size: Option<usize>,
}
fn extract_input_info(input: &Argument) -> InputInfo {
match &input.ty {
ArgType::Tensor(tensor) => InputInfo {
dtype: tensor.dtype,
is_shape: false,
shape_size: None,
},
ArgType::Shape(size) => InputInfo {
dtype: crate::ir::DType::I64,
is_shape: true,
shape_size: Some(*size),
},
ArgType::ScalarTensor(dtype) | ArgType::ScalarNative(dtype) => {
InputInfo {
dtype: *dtype,
is_shape: false,
shape_size: None,
}
}
}
}
fn determine_output_type(
input: &Argument,
input_info: &InputInfo,
output_rank: usize,
static_shape: Option<Vec<Option<usize>>>,
node: &RawNode,
) -> ArgType {
if output_rank == 0 {
return ArgType::ScalarNative(input_info.dtype);
}
if matches!(input.ty, ArgType::ScalarNative(_))
&& output_rank == 1
&& let Some(shape_values) = get_static_shape(node)
{
if shape_values.len() == 1 && (shape_values[0] == -1 || shape_values[0] == 1) {
return ArgType::ScalarNative(input_info.dtype);
}
}
if input_info.is_shape && output_rank == 1 && input_info.dtype == crate::ir::DType::I64 {
let output_size =
calculate_shape_output_size(input_info.shape_size.unwrap_or(1), node, &static_shape);
return ArgType::Shape(output_size);
}
ArgType::Tensor(TensorType {
rank: output_rank,
static_shape,
dtype: input_info.dtype,
})
}
fn calculate_shape_output_size(
input_size: usize,
node: &RawNode,
static_shape: &Option<Vec<Option<usize>>>,
) -> usize {
if let Some(shape_values) = get_static_shape(node)
&& shape_values.len() == 1
{
return match shape_values[0] {
-1 => input_size, n if n > 0 => n as usize,
_ => 1, };
}
if let Some(shape) = static_shape
&& shape.len() == 1
&& let Some(dim) = shape[0]
{
return dim;
}
input_size
}
fn infer_reshape_output_rank(node: &RawNode) -> usize {
if let Some(shape) = get_static_shape(node) {
return shape.len();
}
if let Some(rank) = get_rank_from_shape_input(node) {
return rank;
}
if let Some(rank) = get_rank_from_output(node) {
return rank;
}
panic!(
"Reshape node {} has dynamic shape with no rank information available. \
Cannot determine output rank.",
node.name
)
}
fn get_rank_from_shape_input(node: &RawNode) -> Option<usize> {
if node.inputs.len() != 2 {
return None;
}
match &node.inputs[1].ty {
ArgType::Shape(rank) => Some(*rank),
ArgType::Tensor(tensor) => tensor
.static_shape
.as_ref()
.filter(|dims| !dims.is_empty())
.and_then(|dims| dims[0]),
ArgType::ScalarTensor(_) => Some(1),
_ => None,
}
}
fn get_rank_from_output(node: &RawNode) -> Option<usize> {
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => Some(tensor.rank),
ArgType::ScalarNative(_) => Some(0),
_ => None,
}
}
fn get_static_shape(node: &RawNode) -> Option<Vec<i64>> {
if node.inputs.len() >= 2
&& let Some(value) = node.inputs[1].value()
{
return value.to_i64_vec().ok();
}
if let Some(attr) = node.attrs.get("shape") {
return Some(attr.clone().into_i64s());
}
None
}
pub(crate) struct ReshapeProcessor;
impl NodeProcessor for ReshapeProcessor {
type Config = ReshapeConfig;
fn is_noop(&self, node: &RawNode) -> bool {
if matches!(node.inputs[0].ty, ArgType::ScalarNative(_))
&& matches!(node.outputs[0].ty, ArgType::ScalarNative(_))
{
return true;
}
if let (ArgType::Tensor(input_t), ArgType::Tensor(output_t)) =
(&node.inputs[0].ty, &node.outputs[0].ty)
&& let (Some(in_shape), Some(out_shape)) =
(&input_t.static_shape, &output_t.static_shape)
{
return in_shape == out_shape;
}
false
}
fn spec(&self) -> NodeSpec {
NodeSpec {
min_opset: 1,
max_opset: None,
inputs: InputSpec::AtLeast(1),
outputs: OutputSpec::Exact(1),
}
}
fn lift_constants(&self, node: &mut RawNode, _opset: usize) -> Result<(), ProcessError> {
if node.inputs.len() > 1 && node.inputs[1].is_constant() {
node.inputs[1].to_static()?;
}
Ok(())
}
fn input_preferences(
&self,
node: &RawNode,
_opset: usize,
) -> Result<Option<InputPreferences>, ProcessError> {
use crate::processor::ArgPreference;
if node.inputs.len() < 2 {
return Ok(None);
}
Ok(Some(
InputPreferences::new().add(&node.inputs[1].name, ArgPreference::Shape),
))
}
fn infer_types(
&self,
node: &mut RawNode,
_opset: usize,
_output_preferences: &OutputPreferences,
) -> Result<(), ProcessError> {
if node.inputs.len() >= 2 {
match &node.inputs[1].ty {
ArgType::Tensor(t) => {
if t.rank != 1 {
return Err(ProcessError::Custom(format!(
"Reshape: shape tensor must be 1D, got rank {}",
t.rank
)));
}
if t.dtype != crate::ir::DType::I64 {
return Err(ProcessError::TypeMismatch {
expected: "Shape tensor with dtype I64".to_string(),
actual: format!("Shape tensor with dtype {:?}", t.dtype),
});
}
}
ArgType::Shape(_) => {
}
ArgType::ScalarTensor(dtype) => {
if *dtype != crate::ir::DType::I64 {
return Err(ProcessError::TypeMismatch {
expected: "Shape ScalarTensor with dtype I64".to_string(),
actual: format!("ScalarTensor with dtype {:?}", dtype),
});
}
}
_ => {
return Err(ProcessError::TypeMismatch {
expected: "Tensor, Shape, or ScalarTensor for shape input".to_string(),
actual: format!("{:?}", node.inputs[1].ty),
});
}
}
}
if let Some(shape_values) = get_static_shape(node) {
let neg_one_count = shape_values.iter().filter(|&&v| v == -1).count();
if neg_one_count > 1 {
return Err(ProcessError::Custom(
"Reshape: shape can contain at most one -1 value".to_string(),
));
}
let mut allowzero = 0i64;
for (key, value) in node.attrs.iter() {
if key.as_str() == "allowzero" {
allowzero = value.clone().into_i64();
break;
}
}
if allowzero == 1 {
let has_zero = shape_values.contains(&0);
let has_neg_one = shape_values.contains(&(-1));
if has_zero && has_neg_one {
return Err(ProcessError::InvalidAttribute {
name: "allowzero".to_string(),
reason: "When allowzero=1, shape cannot contain both 0 and -1".to_string(),
});
}
}
}
let input_info = extract_input_info(&node.inputs[0]);
let output_rank = infer_reshape_output_rank(node);
let allowzero = node
.attrs
.get("allowzero")
.map(|v| v.clone().into_i64())
.unwrap_or(0);
let static_shape = if let Some(shape_values) = get_static_shape(node) {
let input_static = match &node.inputs[0].ty {
ArgType::Tensor(t) => t.static_shape.as_ref(),
_ => None,
};
let mut dims: Vec<Option<usize>> = shape_values
.iter()
.enumerate()
.map(|(i, &v)| {
if v > 0 {
Some(v as usize)
} else if v == 0 {
if allowzero == 1 {
Some(0)
} else {
input_static.and_then(|s| s.get(i).copied().flatten())
}
} else {
None
}
})
.collect();
if shape_values.contains(&-1) {
let input_total = input_static
.and_then(|s| s.iter().try_fold(1usize, |acc, dim| dim.map(|d| acc * d)));
if let Some(total) = input_total {
let known_product: Option<usize> = dims
.iter()
.filter(|d| d.is_some())
.try_fold(1usize, |acc, d| d.map(|v| acc * v));
if let Some(product) = known_product
&& product > 0
&& total % product == 0
{
let inferred = total / product;
for (i, v) in shape_values.iter().enumerate() {
if *v == -1 {
dims[i] = Some(inferred);
}
}
}
}
}
Some(dims)
} else {
match &node.outputs[0].ty {
ArgType::Tensor(t) => t.static_shape.clone(),
_ => None,
}
};
node.outputs[0].ty = determine_output_type(
&node.inputs[0],
&input_info,
output_rank,
static_shape,
node,
);
Ok(())
}
fn extract_config(&self, node: &RawNode, _opset: usize) -> Result<Self::Config, ProcessError> {
if node.inputs.len() < 2 {
if let Some(attr) = node.attrs.get("shape") {
let shape = attr.clone().into_i64s();
return Ok(ReshapeConfig {
shape: ReshapeInput::Static(shape),
});
}
return Err(ProcessError::Custom(
"Reshape: shape must be provided as either attribute or input".to_string(),
));
}
let shape = match &node.inputs[1].ty {
ArgType::Tensor(_tensor) => {
match node.inputs[1].value() {
Some(tensor_data) => {
assert_eq!(
tensor_data.shape.len(),
1,
"Reshape: shape tensor must be 1D"
);
ReshapeInput::Static(tensor_data.to_vec::<i64>().unwrap())
}
None => {
ReshapeInput::Runtime(RuntimeInputRef::new(node.inputs[1].name.clone(), 1))
}
}
}
ArgType::Shape(_) => {
match node.inputs[1].value() {
Some(tensor_data) => ReshapeInput::Static(tensor_data.to_vec::<i64>().unwrap()),
None => {
ReshapeInput::Runtime(RuntimeInputRef::new(node.inputs[1].name.clone(), 1))
}
}
}
ArgType::ScalarTensor(_) => {
match node.inputs[1].value() {
Some(tensor_data) => ReshapeInput::Static(tensor_data.to_vec::<i64>().unwrap()),
None => {
ReshapeInput::Runtime(RuntimeInputRef::new(node.inputs[1].name.clone(), 1))
}
}
}
_ => {
return Err(ProcessError::TypeMismatch {
expected: "Tensor, Shape, or ScalarTensor".to_string(),
actual: format!("{:?}", node.inputs[1].ty),
});
}
};
let config = ReshapeConfig { shape };
Ok(config)
}
fn build_node(&self, builder: RawNode, opset: usize) -> Node {
let config = self
.extract_config(&builder, opset)
.expect("Config extraction failed");
Node::Reshape(ReshapeNode {
name: builder.name,
inputs: builder.inputs,
outputs: builder.outputs,
config,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::DType;
use crate::ir::NodeType;
use crate::node::test_utils::TestNodeBuilder;
fn create_test_node(allowzero: i64, shape_vec: Vec<i64>) -> TestNodeBuilder {
let mut builder = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 4, None)
.input_tensor_i64_data("shape", shape_vec.clone(), vec![shape_vec.len()])
.output_tensor_f32("reshaped", 2, None);
if allowzero != 0 {
builder = builder.attr_int("allowzero", allowzero);
}
builder
}
fn create_runtime_reshape_node() -> TestNodeBuilder {
TestNodeBuilder::new(NodeType::Reshape, "test_runtime_reshape")
.input_tensor_f32("data", 2, None)
.input_tensor_i64("shape", 0, None) .output_tensor_f32("reshaped", 2, None)
}
fn create_reshape_with_shape_input() -> TestNodeBuilder {
TestNodeBuilder::new(NodeType::Reshape, "test_reshape_with_shape")
.input_tensor_f32("data", 4, None)
.add_input("shape", ArgType::Shape(2))
.output_tensor_f32("reshaped", 2, None)
}
#[test]
fn test_reshape_config_basic() {
let node = create_test_node(0, vec![2, 3]).process(ReshapeProcessor, 16);
let processor = ReshapeProcessor;
let config = processor.extract_config(&node, 16).unwrap();
match &config.shape {
ReshapeInput::Static(shape) => assert_eq!(shape, &vec![2, 3]),
_ => panic!("Expected static shape"),
}
}
#[test]
fn test_reshape_config_allowzero_supported() {
let _node = create_test_node(1, vec![2, 3]).process(ReshapeProcessor, 16);
}
#[test]
fn test_reshape_config_runtime() {
let node = create_runtime_reshape_node().process(ReshapeProcessor, 16);
let processor = ReshapeProcessor;
let config = processor.extract_config(&node, 16).unwrap();
match &config.shape {
ReshapeInput::Runtime(runtime_ref) => assert_eq!(runtime_ref.name, "shape"),
_ => panic!("Expected runtime shape"),
}
}
#[test]
fn test_reshape_config_no_shape_input() {
let mut node = create_test_node(0, vec![2, 3]).build_with_graph_data(16);
node.inputs.pop(); let processor = ReshapeProcessor;
let result = processor.extract_config(&node, 16);
assert!(matches!(result, Err(ProcessError::Custom(_))));
}
#[test]
#[should_panic(expected = "shape tensor must be 1D")]
fn test_reshape_config_invalid_shape_dim() {
let node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 4, None)
.input_tensor_with_data(
"shape",
DType::I64,
2, crate::ir::TensorData::new(vec![2i64, 3], vec![2, 1]), )
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let _ = processor.extract_config(&node, 16);
}
#[test]
fn test_reshape_config_with_shape_type() {
let node = create_reshape_with_shape_input().process(ReshapeProcessor, 16);
let processor = ReshapeProcessor;
let config = processor.extract_config(&node, 16).unwrap();
match &config.shape {
ReshapeInput::Runtime(runtime_ref) => assert_eq!(runtime_ref.name, "shape"),
_ => panic!("Expected runtime shape"),
}
}
#[test]
fn test_reshape_config_with_shape_type_static_value() {
let node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape_shape_static")
.input_tensor_f32("data", 4, None)
.input_shape_with_data("shape", vec![2, 3, -1])
.output_tensor_f32("reshaped", 3, None)
.process(ReshapeProcessor, 16);
let processor = ReshapeProcessor;
let config = processor.extract_config(&node, 16).unwrap();
match &config.shape {
ReshapeInput::Static(shape) => assert_eq!(shape, &vec![2, 3, -1]),
other => panic!("Expected static shape, got {:?}", other),
}
}
#[test]
fn test_reshape_dynamic_shape_with_output_rank() {
let node = TestNodeBuilder::new(NodeType::Reshape, "test_dynamic_reshape")
.input_tensor_f32("data", 2, None)
.input_tensor_i64("shape", 1, None) .output_tensor_f32("reshaped", 4, None) .build();
match &node.inputs[1].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.rank, 1);
assert_eq!(tensor.static_shape, None); }
_ => panic!("Expected tensor shape input"),
}
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.rank, 4);
assert_eq!(tensor.static_shape, None); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_scalar_to_neg1_keeps_scalar() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape_scalar")
.add_input("data", ArgType::ScalarNative(DType::F32))
.input_tensor_i64_data("shape", vec![-1], vec![1])
.add_output(
"reshaped",
ArgType::Tensor(TensorType::new(DType::F32, 1, None)),
)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::ScalarNative(dtype) => {
assert_eq!(*dtype, DType::F32);
}
other => panic!("Expected Scalar output, got {:?}", other),
}
}
#[test]
fn test_reshape_scalar_to_1_keeps_scalar() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape_scalar_1")
.add_input("data", ArgType::ScalarNative(DType::I64))
.input_tensor_i64_data("shape", vec![1], vec![1])
.add_output(
"reshaped",
ArgType::Tensor(TensorType::new(DType::I64, 1, None)),
)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::ScalarNative(dtype) => {
assert_eq!(*dtype, DType::I64);
}
other => panic!("Expected Scalar output, got {:?}", other),
}
}
#[test]
fn test_reshape_scalar_to_multi_element_becomes_tensor() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape_scalar_2")
.add_input("data", ArgType::ScalarNative(DType::F32))
.input_tensor_i64_data("shape", vec![2], vec![1])
.add_output(
"reshaped",
ArgType::Tensor(TensorType::new(DType::F32, 1, None)),
)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.dtype, DType::F32);
assert_eq!(tensor.rank, 1);
}
other => panic!("Expected Tensor output, got {:?}", other),
}
}
#[test]
fn test_reshape_is_noop_scalar_to_scalar() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape_noop")
.add_input("data", ArgType::ScalarNative(DType::F32))
.input_tensor_i64_data("shape", vec![-1], vec![1])
.add_output(
"reshaped",
ArgType::Tensor(TensorType::new(DType::F32, 1, None)),
)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
assert!(processor.is_noop(&node));
}
#[test]
fn test_reshape_is_not_noop_tensor() {
let node = create_test_node(0, vec![2, 3]).process(ReshapeProcessor, 16);
let processor = ReshapeProcessor;
assert!(!processor.is_noop(&node));
}
#[test]
fn test_reshape_same_static_shape_is_noop() {
let shape: Vec<Option<usize>> = vec![Some(2), Some(3), Some(4)];
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 3, None)
.input_tensor_i64_data("shape", vec![2, 3, 4], vec![3])
.output_tensor_f32("reshaped", 3, None)
.build();
if let ArgType::Tensor(ref mut t) = node.inputs[0].ty {
t.static_shape = Some(shape.clone());
}
if let ArgType::Tensor(ref mut t) = node.outputs[0].ty {
t.static_shape = Some(shape);
}
let processor = ReshapeProcessor;
assert!(processor.is_noop(&node));
}
#[test]
fn test_reshape_different_static_shape_is_not_noop() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 3, None)
.input_tensor_i64_data("shape", vec![6, 4], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build();
if let ArgType::Tensor(ref mut t) = node.inputs[0].ty {
t.static_shape = Some(vec![Some(2), Some(3), Some(4)]);
}
if let ArgType::Tensor(ref mut t) = node.outputs[0].ty {
t.static_shape = Some(vec![Some(6), Some(4)]);
}
let processor = ReshapeProcessor;
assert!(!processor.is_noop(&node));
}
#[test]
fn test_reshape_static_shape_positive_values() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 3, Some(vec![2, 3, 4]))
.input_tensor_i64_data("shape", vec![6, 4], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(t) => {
assert_eq!(t.rank, 2);
assert_eq!(t.static_shape, Some(vec![Some(6), Some(4)]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_static_shape_with_neg1() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 3, Some(vec![2, 3, 4]))
.input_tensor_i64_data("shape", vec![4, -1], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(t) => {
assert_eq!(t.rank, 2);
assert_eq!(t.static_shape, Some(vec![Some(4), Some(6)]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_static_shape_with_zero() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 3, Some(vec![2, 3, 4]))
.input_tensor_i64_data("shape", vec![0, 12], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(t) => {
assert_eq!(t.rank, 2);
assert_eq!(t.static_shape, Some(vec![Some(2), Some(12)]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_static_shape_unknown_input() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 2, None)
.input_tensor_i64_data("shape", vec![3, 4], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(t) => {
assert_eq!(t.rank, 2);
assert_eq!(t.static_shape, Some(vec![Some(3), Some(4)]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_neg1_without_input_shape() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 2, None)
.input_tensor_i64_data("shape", vec![-1, 4], vec![2])
.output_tensor_f32("reshaped", 2, None)
.build_with_graph_data(16);
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
match &node.outputs[0].ty {
ArgType::Tensor(t) => {
assert_eq!(t.rank, 2);
assert_eq!(t.static_shape, Some(vec![None, Some(4)]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_reshape_scalar_tensor_shape_input() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 2, None)
.add_input("shape", ArgType::ScalarTensor(DType::I64))
.output_tensor_f32("reshaped", 0, None)
.build();
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
processor.infer_types(&mut node, 16, &prefs).unwrap();
let config = processor.extract_config(&node, 16).unwrap();
assert!(matches!(config.shape, ReshapeInput::Runtime(_)));
}
#[test]
fn test_reshape_scalar_tensor_wrong_dtype() {
let mut node = TestNodeBuilder::new(NodeType::Reshape, "test_reshape")
.input_tensor_f32("data", 2, None)
.add_input("shape", ArgType::ScalarTensor(DType::F32))
.output_tensor_f32("reshaped", 0, None)
.build();
let processor = ReshapeProcessor;
let prefs = OutputPreferences::new();
let result = processor.infer_types(&mut node, 16, &prefs);
assert!(matches!(result, Err(ProcessError::TypeMismatch { .. })));
}
}