diff --git a/crates/plotnik-lib/src/bytecode/module.rs b/crates/plotnik-lib/src/bytecode/module.rs index 5c79a13e..fcdfbfd3 100644 --- a/crates/plotnik-lib/src/bytecode/module.rs +++ b/crates/plotnik-lib/src/bytecode/module.rs @@ -479,6 +479,18 @@ impl<'a> TypesView<'a> { let count = def.count as usize; (0..count).map(move |i| self.get_member(start + i)) } + + /// Unwrap Optional wrapper and return (inner_type, is_optional). + /// If not Optional, returns (type_id, false). + pub fn unwrap_optional(&self, type_id: QTypeId) -> (QTypeId, bool) { + let Some(type_def) = self.get(type_id) else { + return (type_id, false); + }; + if !type_def.is_optional() { + return (type_id, false); + } + (QTypeId(type_def.data), true) + } } /// View into entrypoints. diff --git a/crates/plotnik-lib/src/bytecode/type_meta.rs b/crates/plotnik-lib/src/bytecode/type_meta.rs index 7c5ac9e3..43d7d89c 100644 --- a/crates/plotnik-lib/src/bytecode/type_meta.rs +++ b/crates/plotnik-lib/src/bytecode/type_meta.rs @@ -95,6 +95,12 @@ impl TypeDef { TypeKind::from_u8(self.kind).is_some_and(|k| k.is_alias()) } + /// Whether this is an Optional wrapper type. + #[inline] + pub fn is_optional(&self) -> bool { + TypeKind::from_u8(self.kind).is_some_and(|k| k == TypeKind::Optional) + } + /// For alias types, get the target type. #[inline] pub fn alias_target(&self) -> Option { diff --git a/crates/plotnik-lib/src/lib.rs b/crates/plotnik-lib/src/lib.rs index 10c3e438..05278d97 100644 --- a/crates/plotnik-lib/src/lib.rs +++ b/crates/plotnik-lib/src/lib.rs @@ -18,13 +18,13 @@ pub mod analyze; pub mod bytecode; -pub mod typegen; pub mod compile; pub mod diagnostics; pub mod emit; pub mod parser; pub mod query; pub mod type_system; +pub mod typegen; /// Result type for analysis passes that produce both output and diagnostics. /// diff --git a/crates/plotnik-lib/src/typegen/typescript.rs b/crates/plotnik-lib/src/typegen/typescript.rs index 6d6cc0bd..2ac35f62 100644 --- a/crates/plotnik-lib/src/typegen/typescript.rs +++ b/crates/plotnik-lib/src/typegen/typescript.rs @@ -236,7 +236,7 @@ impl<'a> Emitter<'a> { contexts.entry(type_id).or_insert_with(|| ctx.clone()); for member in self.types.members_of(&type_def) { let field_name = self.strings.get(member.name); - let (inner_type, _) = self.unwrap_optional(member.type_id); + let (inner_type, _) = self.types.unwrap_optional(member.type_id); let field_ctx = NamingContext { def_name: ctx.def_name.clone(), field_name: Some(field_name.to_string()), @@ -532,7 +532,7 @@ impl<'a> Emitter<'a> { .members_of(type_def) .map(|member| { let field_name = self.strings.get(member.name).to_string(); - let (inner_type, optional) = self.unwrap_optional(member.type_id); + let (inner_type, optional) = self.types.unwrap_optional(member.type_id); (field_name, inner_type, optional) }) .collect(); @@ -660,7 +660,7 @@ impl<'a> Emitter<'a> { .members_of(type_def) .map(|member| { let field_name = self.strings.get(member.name).to_string(); - let (inner_type, optional) = self.unwrap_optional(member.type_id); + let (inner_type, optional) = self.types.unwrap_optional(member.type_id); (field_name, inner_type, optional) }) .collect(); @@ -712,17 +712,6 @@ impl<'a> Emitter<'a> { } } - /// Unwrap Optional wrappers and return (inner_type, is_optional). - fn unwrap_optional(&self, type_id: QTypeId) -> (QTypeId, bool) { - let Some(type_def) = self.types.get(type_id) else { - return (type_id, false); - }; - if type_def.type_kind() != Some(TypeKind::Optional) { - return (type_id, false); - } - (QTypeId(type_def.data), true) - } - fn needs_generated_name(&self, type_def: &TypeDef) -> bool { matches!( type_def.type_kind(),