From 4e2b107fe7b71a7a6589ac136793ba463961a515 Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Mon, 9 Nov 2026 18:17:41 -0500 Subject: [PATCH 1/6] modifications to assure distinction between annotation and annotations withheld for Arguments of type SSAValue --- crates/kirin-derive-chumsky/src/validation.rs | 2 +- .../src/ir/fields/data.rs | 5 ++++- .../src/ir/fields/info.rs | 22 +++++++++++-------- .../src/ir/statement/definition.rs | 4 +--- .../src/template/builder_template/helpers.rs | 2 +- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/crates/kirin-derive-chumsky/src/validation.rs b/crates/kirin-derive-chumsky/src/validation.rs index 2138bc7a7..b02d75ba6 100644 --- a/crates/kirin-derive-chumsky/src/validation.rs +++ b/crates/kirin-derive-chumsky/src/validation.rs @@ -624,7 +624,7 @@ mod tests { ident: Some(syn::Ident::new(name, proc_macro2::Span::call_site())), collection: Collection::Single, data: FieldData::Argument { - ssa_type: syn::parse_quote!(Placeholder::placeholder()), + ssa_type: Some(syn::parse_quote!(Placeholder::placeholder())), }, } } diff --git a/crates/kirin-derive-toolkit/src/ir/fields/data.rs b/crates/kirin-derive-toolkit/src/ir/fields/data.rs index d1736a279..cd7a986fc 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/data.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/data.rs @@ -41,8 +41,11 @@ impl FieldCategory { #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum FieldData { + // The SSA input value. The ssa_type is None when the field does not have + // #[kirin(type=...)]. This results in not verifying the input type. Does not + // default to T::Placeholder. Argument { - ssa_type: syn::Expr, + ssa_type: Option, }, /// SSA output value. When the user omits `#[kirin(type = ...)]`, `ssa_type` /// defaults to `ir_type::placeholder()` (from the enum/struct-level diff --git a/crates/kirin-derive-toolkit/src/ir/fields/info.rs b/crates/kirin-derive-toolkit/src/ir/fields/info.rs index b3a095ff0..d6cc03467 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/info.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/info.rs @@ -11,7 +11,7 @@ use super::{Collection, FieldCategory, FieldData}; /// /// ```ignore /// match field.category() { -/// FieldCategory::Argument => { /* field.ssa_type() is Some */ } +/// FieldCategory::Argument => { /* field.ssa_type() is Some only if annotated */ } /// FieldCategory::Value => { /* field.value_type() is Some */ } /// _ => {} /// } @@ -99,9 +99,11 @@ impl FieldInfo { } /// Return the SSA type expression for `Argument` or `Result` fields. + // An argument without #[kirin(type=...)] returns None pub fn ssa_type(&self) -> Option<&syn::Expr> { match &self.data { - FieldData::Argument { ssa_type } | FieldData::Result { ssa_type, .. } => Some(ssa_type), + FieldData::Argument { ssa_type } => ssa_type.as_ref(), + FieldData::Result { ssa_type, .. } => Some(ssa_type), _ => None, } } @@ -160,7 +162,7 @@ mod tests { ident: name.map(|n| syn::Ident::new(n, Span::call_site())), collection: Collection::Single, data: FieldData::Argument { - ssa_type: syn::parse_quote!(Placeholder::placeholder()), + ssa_type: Some(syn::parse_quote!(Placeholder::placeholder())), }, } } @@ -222,12 +224,7 @@ mod tests { #[test] fn kind_name_all_categories() { let cases: Vec<(FieldData, &str)> = vec![ - ( - FieldData::Argument { - ssa_type: syn::parse_quote!(()), - }, - "argument", - ), + (FieldData::Argument { ssa_type: None }, "argument"), ( FieldData::Result { ssa_type: syn::parse_quote!(()), @@ -317,6 +314,13 @@ mod tests { assert!(f.ssa_type().is_some()); } + #[test] + fn ssa_type_on_unannotated_argument() { + let mut f = make_argument_field(0, Some("x")); + f.data = FieldData::Argument { ssa_type: None }; + assert!(f.ssa_type().is_none()); + } + #[test] fn ssa_type_on_result() { let f = make_result_field(0, "out"); diff --git a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs index 88a3065dd..0ef6052f6 100644 --- a/crates/kirin-derive-toolkit/src/ir/statement/definition.rs +++ b/crates/kirin-derive-toolkit/src/ir/statement/definition.rs @@ -217,9 +217,7 @@ impl Statement { ident, collection, data: FieldData::Argument { - ssa_type: kirin_opts - .ssa_ty - .unwrap_or_else(|| syn::parse_quote! { () }), + ssa_type: kirin_opts.ssa_ty, }, }); } diff --git a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs index 61961fc61..d242c5652 100644 --- a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs +++ b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs @@ -753,7 +753,7 @@ mod tests { ident: Some(syn::Ident::new(name, proc_macro2::Span::call_site())), collection: Collection::Single, data: FieldData::Argument { - ssa_type: syn::parse_quote!(MyType::placeholder()), + ssa_type: Some(syn::parse_quote!(MyType::Placeholder())), }, } } From 4b9031efa26d6c40c2fab4a557208acc474fd65a Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Mon, 9 Nov 2026 18:30:54 -0500 Subject: [PATCH 2/6] added a check that checks each annotated single SSAValue operant has the operand type. Similar to what ResultValue does. --- .../src/template/builder_template/helpers.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs index d242c5652..d0a6c89f8 100644 --- a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs +++ b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs @@ -261,6 +261,26 @@ fn statement_id_name(info: &StatementInfo) -> syn::Ident { format_ident!("{}_statement_id", name, span = info.name.span()) } +fn build_fn_arg_type_checks(info: &StatementInfo) -> Vec { + info.fields + .iter() + .filter(|field| { + field.category() == FieldCategory::Argument + && matches!(field.collection, Collection::Single) + }) + .filter_map(|field| { + let expected = field.ssa_type()?; + let name = field.name_ident(info.name.span()); + let label = format!("{}::{}", info.name, field); + Some(quote! { + if let Some(actual) = stage.ssa_arena().get(#name).and_then(|i|i.ty()) { + let expected = Lang::Type::from(#expected); + assert!(*actual == expected, "{}: expected operand of type {:?}, got {:?}", #label, expected, actual); + } + }) + }).collect() +} + fn build_fn_body( info: &StatementInfo, input: &InputMeta, @@ -269,6 +289,7 @@ fn build_fn_body( ) -> proc_macro2::TokenStream { let statement_id = statement_id_name(info); let let_inputs = build_fn_let_inputs(info, &input.ir_type); + let arg_type_checks = build_fn_arg_type_checks(info); let let_results = let_name_eq_result_value(info, result_name_map, crate_path); let is_tuple = info.fields.iter().all(|f| f.ident.is_none()); @@ -306,6 +327,7 @@ fn build_fn_body( quote! {{ let #statement_id = stage.statement_arena().next_id(); #(#let_inputs)* + #(#arg_type_checks)* #let_results stage From 8611a5577dd985e4684c4bcb8738a5ae9729b51c Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Mon, 9 Nov 2026 18:47:36 -0500 Subject: [PATCH 3/6] asked claude to generate test cases for the recent set of changes. Add test cases and fake language into tests/ and crates/kirin-test-languages --- crates/kirin-test-languages/src/lib.rs | 4 ++ .../src/typed_operand_language.rs | 22 +++++++++ tests/builder_operand_types.rs | 45 +++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 crates/kirin-test-languages/src/typed_operand_language.rs create mode 100644 tests/builder_operand_types.rs diff --git a/crates/kirin-test-languages/src/lib.rs b/crates/kirin-test-languages/src/lib.rs index a60b06952..0be9e6fd4 100644 --- a/crates/kirin-test-languages/src/lib.rs +++ b/crates/kirin-test-languages/src/lib.rs @@ -17,6 +17,8 @@ mod namespaced_language; mod scalar_function_language; #[cfg(feature = "simple-language")] mod simple_language; +#[cfg(feature = "simple-language")] +mod typed_operand_language; #[cfg(feature = "ungraph-language")] mod ungraph_language; @@ -36,6 +38,8 @@ pub use namespaced_language::NamespacedLanguage; pub use scalar_function_language::ScalarFunctionLanguage; #[cfg(feature = "simple-language")] pub use simple_language::SimpleLanguage; +#[cfg(feature = "simple-language")] +pub use typed_operand_language::TypedOperandLanguage; #[cfg(feature = "ungraph-language")] pub use ungraph_language::{ UngraphCompound, UngraphEdge, UngraphLanguage, UngraphNodeA, UngraphNodeB, diff --git a/crates/kirin-test-languages/src/typed_operand_language.rs b/crates/kirin-test-languages/src/typed_operand_language.rs new file mode 100644 index 000000000..9408e9f00 --- /dev/null +++ b/crates/kirin-test-languages/src/typed_operand_language.rs @@ -0,0 +1,22 @@ +use crate::SimpleType; +use kirin_ir::{Dialect, ResultValue, SSAValue}; + +/// Operands with `#[kirin(type = ...)]`, so the generated builders check them. +#[derive(Clone, Debug, PartialEq, Dialect)] +#[kirin(builders, type = SimpleType, crate = kirin_ir)] +pub enum TypedOperandLanguage { + /// The first operand must be `I64`. The second has no annotation and is never checked. + Index( + #[kirin(type = SimpleType::I64)] SSAValue, + SSAValue, + #[kirin(type = SimpleType::F64)] ResultValue, + ), + /// The expected type comes from another field (`SimpleType` isn't `Copy`). + Cast { + #[kirin(type = target.clone())] + input: SSAValue, + target: SimpleType, + #[kirin(type = target.clone())] + result: ResultValue, + }, +} diff --git a/tests/builder_operand_types.rs b/tests/builder_operand_types.rs new file mode 100644 index 000000000..8aac74c28 --- /dev/null +++ b/tests/builder_operand_types.rs @@ -0,0 +1,45 @@ +//! Generated builders check annotated `SSAValue` operands against their +//! `#[kirin(type = ...)]`, and skip operands whose type isn't set yet. + +use kirin::prelude::*; +use kirin_ir::BuilderSSAKind; +use kirin_test_languages::{SimpleType, TypedOperandLanguage as L}; + +fn typed(stage: &mut BuilderStageInfo, ty: SimpleType) -> SSAValue { + stage.ssa().ty(ty).kind(BuilderSSAKind::Test).new() +} + +#[test] +fn accepts_operand_of_annotated_type() { + let mut stage = BuilderStageInfo::::default(); + let idx = typed(&mut stage, SimpleType::I64); + let any = typed(&mut stage, SimpleType::Bool); // unannotated, never checked + L::op_index(&mut stage, idx, any); +} + +#[test] +fn accepts_untyped_placeholders() { + let mut stage = BuilderStageInfo::::default(); + let idx = stage.block_argument().index(0); + let port = stage.graph_port().index(0); + L::op_index(&mut stage, idx, port); + let port = stage.graph_port().name("x"); + L::op_cast(&mut stage, port, SimpleType::F32); +} + +#[test] +#[should_panic(expected = "Index::field_0: expected operand of type I64, got F32")] +fn rejects_operand_of_wrong_type() { + let mut stage = BuilderStageInfo::::default(); + let idx = typed(&mut stage, SimpleType::F32); + let any = typed(&mut stage, SimpleType::Bool); + L::op_index(&mut stage, idx, any); +} + +#[test] +#[should_panic(expected = "Cast::input: expected operand of type F32, got I64")] +fn rejects_operand_against_field_computed_type() { + let mut stage = BuilderStageInfo::::default(); + let input = typed(&mut stage, SimpleType::I64); + L::op_cast(&mut stage, input, SimpleType::F32); +} From d7f8997ee49ac69bbf96bd4fc93d6c19c2443550 Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Mon, 9 Nov 2026 18:52:20 -0500 Subject: [PATCH 4/6] fixed comments, didn't realize they were done in /// fashion for rustdoc. and fixed a typo --- crates/kirin-derive-toolkit/src/ir/fields/data.rs | 6 +++--- crates/kirin-derive-toolkit/src/ir/fields/info.rs | 2 +- .../src/template/builder_template/helpers.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/kirin-derive-toolkit/src/ir/fields/data.rs b/crates/kirin-derive-toolkit/src/ir/fields/data.rs index cd7a986fc..23a85dc10 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/data.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/data.rs @@ -41,9 +41,9 @@ impl FieldCategory { #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum FieldData { - // The SSA input value. The ssa_type is None when the field does not have - // #[kirin(type=...)]. This results in not verifying the input type. Does not - // default to T::Placeholder. + /// The SSA input value. The `ssa_type` is `None` when the field does not have + /// `#[kirin(type = ...)]`. This results in not verifying the input type. Does not + /// default to `T::Placeholder`. Argument { ssa_type: Option, }, diff --git a/crates/kirin-derive-toolkit/src/ir/fields/info.rs b/crates/kirin-derive-toolkit/src/ir/fields/info.rs index d6cc03467..82f36afa0 100644 --- a/crates/kirin-derive-toolkit/src/ir/fields/info.rs +++ b/crates/kirin-derive-toolkit/src/ir/fields/info.rs @@ -99,7 +99,7 @@ impl FieldInfo { } /// Return the SSA type expression for `Argument` or `Result` fields. - // An argument without #[kirin(type=...)] returns None + /// An argument without `#[kirin(type = ...)]` returns `None`. pub fn ssa_type(&self) -> Option<&syn::Expr> { match &self.data { FieldData::Argument { ssa_type } => ssa_type.as_ref(), diff --git a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs index d0a6c89f8..2f1967ea3 100644 --- a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs +++ b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs @@ -775,7 +775,7 @@ mod tests { ident: Some(syn::Ident::new(name, proc_macro2::Span::call_site())), collection: Collection::Single, data: FieldData::Argument { - ssa_type: Some(syn::parse_quote!(MyType::Placeholder())), + ssa_type: Some(syn::parse_quote!(MyType::placeholder())), }, } } From 88f39d8922e3c350f362181d693d153658ecdbe8 Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Mon, 9 Nov 2026 23:08:53 -0500 Subject: [PATCH 5/6] added comment about how a type that does not implement Copy wont compile for the argument --- crates/kirin-derive-toolkit/src/ir/attrs.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/kirin-derive-toolkit/src/ir/attrs.rs b/crates/kirin-derive-toolkit/src/ir/attrs.rs index 32d054f5d..195265e4a 100644 --- a/crates/kirin-derive-toolkit/src/ir/attrs.rs +++ b/crates/kirin-derive-toolkit/src/ir/attrs.rs @@ -77,6 +77,10 @@ pub struct KirinFieldOptions { #[darling(default)] pub into: bool, pub default: Option, + /// SSA type expression. On a `ResultValue` it sets the result's type; on an + /// `SSAValue` the generated builder asserts the operand has this type. + /// Fields named in the expression are moved, so a non-`Copy` field must + /// be written as `field.clone()`. #[darling(rename = "type")] pub ssa_ty: Option, } From d8c28b42412c3e5314646d8118824f832b33766c Mon Sep 17 00:00:00 2001 From: jonhas colina Date: Tue, 10 Nov 2026 11:18:45 -0500 Subject: [PATCH 6/6] added a trait for assuring that targeted types are resolved. In builders, added Typeresolution for stateents with annotated single operants. --- crates/kirin-derive-ir/src/tests/dialect.rs | 26 +++++++++++++++++ .../src/template/builder_template/helpers.rs | 28 ++++++++++++++---- crates/kirin-ir/src/comptime.rs | 8 +++++ crates/kirin-ir/src/lib.rs | 4 +-- crates/kirin-test-types/src/simple_type.rs | 7 +++++ tests/builder_operand_types.rs | 29 +++++++++++++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/crates/kirin-derive-ir/src/tests/dialect.rs b/crates/kirin-derive-ir/src/tests/dialect.rs index 308c73117..cbe0eadd9 100644 --- a/crates/kirin-derive-ir/src/tests/dialect.rs +++ b/crates/kirin-derive-ir/src/tests/dialect.rs @@ -467,3 +467,29 @@ fn test_dialect_derive_enum_mixed_callable_body_and_wraps() { }; insta::assert_snapshot!(generate_dialect_code(input)); } + +#[test] +fn operand_resolution_bound_only_for_checked_fields() { + for field in [ + syn::parse_quote!(input: SSAValue), + syn::parse_quote!(#[kirin(type = T::placeholder())] input: Vec), + syn::parse_quote!(#[kirin(type = T::placeholder())] input: Option), + ] { + let field: syn::Field = field; + let input = syn::parse_quote! { + #[kirin(type = T, builders)] + struct Op { #field } + }; + assert!(!generate_dialect_code(input).contains("TypeResolution")); + } + let input = syn::parse_quote! { + #[kirin(type = T, builders, crate = custom_ir)] + struct Op { + #[kirin(type = T::placeholder())] + input: SSAValue, + } + }; + let code = generate_dialect_code(input); + assert!(code.contains("Lang::Type: custom_ir::TypeResolution")); + assert!(code.contains("custom_ir::TypeResolution::is_resolved")); +} diff --git a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs index 2f1967ea3..596b9835a 100644 --- a/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs +++ b/crates/kirin-derive-toolkit/src/template/builder_template/helpers.rs @@ -261,7 +261,10 @@ fn statement_id_name(info: &StatementInfo) -> syn::Ident { format_ident!("{}_statement_id", name, span = info.name.span()) } -fn build_fn_arg_type_checks(info: &StatementInfo) -> Vec { +fn build_fn_arg_type_checks( + info: &StatementInfo, + crate_path: &syn::Path, +) -> Vec { info.fields .iter() .filter(|field| { @@ -273,9 +276,13 @@ fn build_fn_arg_type_checks(info: &StatementInfo) -> Vec proc_macro2::TokenStream { let statement_id = statement_id_name(info); let let_inputs = build_fn_let_inputs(info, &input.ir_type); - let arg_type_checks = build_fn_arg_type_checks(info); + let arg_type_checks = build_fn_arg_type_checks(info, crate_path); let let_results = let_name_eq_result_value(info, result_name_map, crate_path); let is_tuple = info.fields.iter().all(|f| f.ident.is_none()); @@ -463,6 +470,16 @@ pub(super) fn build_fn_for_statement( let body = build_fn_body(info, input, &result_name_map, crate_path); let self_ty = quote! { #name #ty_generics }; + let resolution_bound = if info.fields.iter().any(|field| { + field.category() == FieldCategory::Argument + && matches!(field.collection, Collection::Single) + && field.ssa_type().is_some() + }) { + quote! { , Lang::Type: #crate_path::TypeResolution } + } else { + quote! {} + }; + let needs_placeholder_bound = info.fields.iter().any(|f| f.is_auto_placeholder()); let placeholder_bound = if needs_placeholder_bound { @@ -479,6 +496,7 @@ pub(super) fn build_fn_for_statement( Lang: #crate_path::Dialect + ::core::convert::From<#self_ty>, Lang::Type: From<#ir_type> #placeholder_bound + #resolution_bound { let stage = stage.as_build_stage(); #body diff --git a/crates/kirin-ir/src/comptime.rs b/crates/kirin-ir/src/comptime.rs index 61ebc6507..f52aeadc6 100644 --- a/crates/kirin-ir/src/comptime.rs +++ b/crates/kirin-ir/src/comptime.rs @@ -46,6 +46,14 @@ pub trait Placeholder: CompileTimeValue { fn placeholder() -> Self; } +/// Whether a type is ready for equality checks in builders. +/// +/// Return false for unknown types or unresolved components. +/// Skipped checks are not retried after inference. +pub trait TypeResolution { + fn is_resolved(&self) -> bool; +} + pub trait Typeof { fn type_of(&self) -> Ty; } diff --git a/crates/kirin-ir/src/lib.rs b/crates/kirin-ir/src/lib.rs index ece86535c..012f9a99b 100644 --- a/crates/kirin-ir/src/lib.rs +++ b/crates/kirin-ir/src/lib.rs @@ -29,7 +29,7 @@ pub use builder::error::{ StagedFunctionConflictKind, StagedFunctionError, }; pub use builder::{AsBuildStage, BuilderStageInfo, FinalizeError}; -pub use comptime::{CompileTimeValue, Placeholder, Typeof}; +pub use comptime::{CompileTimeValue, Placeholder, TypeResolution, Typeof}; pub use derived::{ BlockBody, ChainDefect, ChainFinding, DanglingParent, DeriveError, Finding, Mismatch, VerifyError, verify_derived, @@ -73,7 +73,7 @@ pub mod prelude { }; pub use crate::{ CompileTimeValue, HasProduct, Placeholder, Product, Project, ProjectError, TryProject, - TryProjectTo, Typeof, + TryProjectTo, TypeResolution, Typeof, }; } diff --git a/crates/kirin-test-types/src/simple_type.rs b/crates/kirin-test-types/src/simple_type.rs index 3976cb9df..dc7806d2f 100644 --- a/crates/kirin-test-types/src/simple_type.rs +++ b/crates/kirin-test-types/src/simple_type.rs @@ -13,6 +13,13 @@ pub enum SimpleType { Bottom, } +impl kirin_ir::TypeResolution for SimpleType { + fn is_resolved(&self) -> bool { + // Bottom is unresolved; Any is resolved. + !matches!(self, Self::Bottom) + } +} + impl Lattice for SimpleType { fn is_subseteq(&self, other: &Self) -> bool { self == other || matches!(other, SimpleType::Any) || matches!(self, SimpleType::Bottom) diff --git a/tests/builder_operand_types.rs b/tests/builder_operand_types.rs index 8aac74c28..a7ca18f49 100644 --- a/tests/builder_operand_types.rs +++ b/tests/builder_operand_types.rs @@ -43,3 +43,32 @@ fn rejects_operand_against_field_computed_type() { let input = typed(&mut stage, SimpleType::I64); L::op_cast(&mut stage, input, SimpleType::F32); } + +#[test] +fn accepts_unresolved_actual_type() { + let mut stage = BuilderStageInfo::::default(); + let input = typed(&mut stage, SimpleType::Bottom); + L::op_cast(&mut stage, input, SimpleType::I64); +} + +#[test] +fn accepts_unresolved_expected_type() { + let mut stage = BuilderStageInfo::::default(); + let input = typed(&mut stage, SimpleType::I64); + L::op_cast(&mut stage, input, SimpleType::Bottom); +} + +#[test] +fn accepts_both_types_unresolved() { + let mut stage = BuilderStageInfo::::default(); + let input = typed(&mut stage, SimpleType::Bottom); + L::op_cast(&mut stage, input, SimpleType::Bottom); +} + +#[test] +#[should_panic(expected = "Cast::input: expected operand of type I64, got Any")] +fn resolved_top_type_is_still_checked() { + let mut stage = BuilderStageInfo::::default(); + let input = typed(&mut stage, SimpleType::Any); + L::op_cast(&mut stage, input, SimpleType::I64); +}