Skip to content

Commit ec99367

Browse files
0xrinegadeclaude
andcommitted
feat(ovsm): Add bidirectional type checking and type bridge modules
- New types/bidirectional.rs for bidirectional type inference - New types/bridge.rs for type system bridging - Updates to parser AST and S-expression parser - TUI graph and app improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 4c0fce6 commit ec99367

File tree

11 files changed

+2730
-373
lines changed

11 files changed

+2730
-373
lines changed

crates/ovsm/src/compiler/mod.rs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ pub enum SbpfVersion {
5555
V2,
5656
}
5757

58+
/// Type checking mode for compilation
59+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60+
pub enum TypeCheckMode {
61+
/// No additional type checking (use existing IR-level checker only)
62+
#[default]
63+
Legacy,
64+
/// Gradual typing - untyped code gets Type::Any, no errors for missing annotations
65+
Gradual,
66+
/// Strict typing - all type mismatches are errors
67+
Strict,
68+
}
69+
5870
/// Compilation options
5971
#[derive(Debug, Clone)]
6072
pub struct CompileOptions {
@@ -70,6 +82,8 @@ pub struct CompileOptions {
7082
pub sbpf_version: SbpfVersion,
7183
/// Enable Solana ABI compliant entrypoint with deserialization
7284
pub enable_solana_abi: bool,
85+
/// Enable bidirectional type inference (gradual = false for strict checking)
86+
pub type_check_mode: TypeCheckMode,
7387
}
7488

7589
impl Default for CompileOptions {
@@ -81,6 +95,7 @@ impl Default for CompileOptions {
8195
source_map: false,
8296
sbpf_version: SbpfVersion::V1, // V1 with relocations for comparison
8397
enable_solana_abi: false, // Temporarily disabled while fixing opcode issues
98+
type_check_mode: TypeCheckMode::Legacy, // Use existing checker by default
8499
}
85100
}
86101
}
@@ -100,6 +115,8 @@ pub struct CompileResult {
100115
pub warnings: Vec<String>,
101116
/// Verification result
102117
pub verification: Option<VerifyResult>,
118+
/// Type errors from bidirectional checker (if enabled)
119+
pub type_errors: Vec<String>,
103120
}
104121

105122
/// OVSM to sBPF Compiler
@@ -121,7 +138,38 @@ impl Compiler {
121138
let mut parser = Parser::new(tokens);
122139
let program = parser.parse()?;
123140

124-
// Phase 2: Type check
141+
// Phase 1.5: Bidirectional type checking (if enabled)
142+
let mut type_errors = Vec::new();
143+
if self.options.type_check_mode != TypeCheckMode::Legacy {
144+
use crate::types::BidirectionalChecker;
145+
146+
let mut bidir_checker = match self.options.type_check_mode {
147+
TypeCheckMode::Strict => BidirectionalChecker::strict(),
148+
_ => BidirectionalChecker::new(), // Gradual mode
149+
};
150+
151+
// Run bidirectional type inference
152+
for stmt in &program.statements {
153+
if let crate::parser::Statement::Expression(expr) = stmt {
154+
bidir_checker.synth(expr);
155+
}
156+
}
157+
158+
// Collect any errors
159+
for err in bidir_checker.errors() {
160+
type_errors.push(err.to_string());
161+
}
162+
163+
// In strict mode, fail on type errors
164+
if self.options.type_check_mode == TypeCheckMode::Strict && !type_errors.is_empty() {
165+
return Err(Error::compiler(format!(
166+
"Type errors: {}",
167+
type_errors.join("; ")
168+
)));
169+
}
170+
}
171+
172+
// Phase 2: Type check (legacy IR-level checker)
125173
let mut type_checker = TypeChecker::new();
126174
let typed_program = type_checker.check(&program)?;
127175

@@ -201,11 +249,40 @@ impl Compiler {
201249
sbpf_instruction_count: sbpf_program.len(),
202250
warnings,
203251
verification: Some(verification),
252+
type_errors,
204253
})
205254
}
206255

207256
/// Compile from already-parsed AST
208257
pub fn compile_ast(&self, program: &Program) -> Result<CompileResult> {
258+
// Bidirectional type checking (if enabled)
259+
let mut type_errors = Vec::new();
260+
if self.options.type_check_mode != TypeCheckMode::Legacy {
261+
use crate::types::BidirectionalChecker;
262+
263+
let mut bidir_checker = match self.options.type_check_mode {
264+
TypeCheckMode::Strict => BidirectionalChecker::strict(),
265+
_ => BidirectionalChecker::new(),
266+
};
267+
268+
for stmt in &program.statements {
269+
if let crate::parser::Statement::Expression(expr) = stmt {
270+
bidir_checker.synth(expr);
271+
}
272+
}
273+
274+
for err in bidir_checker.errors() {
275+
type_errors.push(err.to_string());
276+
}
277+
278+
if self.options.type_check_mode == TypeCheckMode::Strict && !type_errors.is_empty() {
279+
return Err(Error::compiler(format!(
280+
"Type errors: {}",
281+
type_errors.join("; ")
282+
)));
283+
}
284+
}
285+
209286
let mut type_checker = TypeChecker::new();
210287
let typed_program = type_checker.check(program)?;
211288

@@ -279,6 +356,7 @@ impl Compiler {
279356
sbpf_instruction_count: sbpf_program.len(),
280357
warnings,
281358
verification: Some(verification),
359+
type_errors,
282360
})
283361
}
284362
}

crates/ovsm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ pub use lexer::{SExprScanner, Token, TokenKind};
294294
pub use parser::{BinaryOp, Expression, Program, SExprParser, Statement, UnaryOp};
295295
pub use runtime::{Environment, LispEvaluator, Value};
296296
pub use tools::{Tool, ToolRegistry};
297-
pub use types::{Type, TypeContext, TypeChecker, TypeError};
297+
pub use types::{Type, TypeContext, TypeChecker, TypeError, BidirectionalChecker, TypeBridge};
298298

299299
// Convenient type aliases for the primary LISP-based interpreter
300300
/// Type alias for the S-expression scanner (lexer).

crates/ovsm/src/parser/ast.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,32 @@ pub enum Expression {
268268
/// Body expressions evaluated with pattern bindings in scope
269269
body: Vec<Expression>,
270270
},
271+
272+
// ============================================================================
273+
// Type System Expressions
274+
// ============================================================================
275+
276+
/// Type annotation expression (: expr type)
277+
/// Explicitly annotates an expression with a type for bidirectional type checking
278+
/// Example: (: 42 u64) or (: (lambda (x) x) (-> i64 i64))
279+
TypeAnnotation {
280+
/// The expression being annotated
281+
expr: Box<Expression>,
282+
/// The type annotation (parsed as an expression for flexibility)
283+
/// Simple: Variable("u64"), Complex: ToolCall for generic types
284+
type_expr: Box<Expression>,
285+
},
286+
287+
/// Typed lambda expression (lambda ((x : T) (y : U)) -> R body)
288+
/// Lambda with explicit parameter types and optional return type
289+
TypedLambda {
290+
/// Parameters with optional type annotations: (name, Option<type_expr>)
291+
typed_params: Vec<(String, Option<Box<Expression>>)>,
292+
/// Optional return type annotation
293+
return_type: Option<Box<Expression>>,
294+
/// Body expression of the lambda
295+
body: Box<Expression>,
296+
},
271297
}
272298

273299
/// Binary operators

crates/ovsm/src/parser/sexpr_parser.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ impl SExprParser {
130130
TokenKind::Dot => self.parse_field_access(),
131131
TokenKind::LeftBracket => self.parse_index_access(),
132132

133+
// Type annotation form (: expr type)
134+
TokenKind::Colon => self.parse_type_annotation(),
135+
136+
// Function type form (-> param-types return-type)
137+
TokenKind::Arrow => self.parse_function_type(),
138+
133139
// Operators
134140
TokenKind::Plus
135141
| TokenKind::Minus
@@ -1029,6 +1035,66 @@ impl SExprParser {
10291035
Ok(Expression::IndexAccess { array, index })
10301036
}
10311037

1038+
/// Parse (: expr type) - type annotation expression
1039+
/// Syntax: (: expression type-expression)
1040+
/// Examples:
1041+
/// (: 42 u64) - annotate integer as u64
1042+
/// (: (+ a b) i64) - annotate arithmetic result
1043+
/// (: (lambda (x) x) (-> i64 i64)) - annotate lambda with function type
1044+
fn parse_type_annotation(&mut self) -> Result<Expression> {
1045+
self.advance(); // consume ':'
1046+
1047+
// Parse the expression being annotated
1048+
let expr = Box::new(self.parse_expression()?);
1049+
1050+
// Parse the type expression
1051+
// Type expressions can be:
1052+
// - Simple types: u64, i32, bool, string, etc.
1053+
// - Generic types: (Array u64), (Option i32)
1054+
// - Function types: (-> i64 i64) or (-> (i64 i64) i64) for multi-param
1055+
// - Pointer types: (Ptr u64), (Ref MyStruct)
1056+
let type_expr = Box::new(self.parse_expression()?);
1057+
1058+
self.consume(TokenKind::RightParen)?;
1059+
1060+
Ok(Expression::TypeAnnotation { expr, type_expr })
1061+
}
1062+
1063+
/// Parse (-> param-types return-type) - function type expression
1064+
/// Syntax:
1065+
/// (-> ReturnType) - Unit/void function
1066+
/// (-> ParamType ReturnType) - Single-param function
1067+
/// (-> Param1 Param2 ReturnType) - Multi-param function (last is return)
1068+
/// Examples:
1069+
/// (-> i64) - () -> i64
1070+
/// (-> i64 i64) - i64 -> i64
1071+
/// (-> i64 i64 bool) - (i64, i64) -> bool
1072+
fn parse_function_type(&mut self) -> Result<Expression> {
1073+
self.advance(); // consume '->'
1074+
1075+
// Collect all type expressions
1076+
let mut types = Vec::new();
1077+
while !self.check(&TokenKind::RightParen) {
1078+
types.push(self.parse_expression()?);
1079+
}
1080+
self.consume(TokenKind::RightParen)?;
1081+
1082+
if types.is_empty() {
1083+
return Err(Error::ParseError(
1084+
"Function type `(-> ...)` requires at least a return type".to_string(),
1085+
));
1086+
}
1087+
1088+
// Last type is return type, rest are parameters
1089+
// Represented as ToolCall for consistency with other type expressions
1090+
let args: Vec<Argument> = types.into_iter().map(Argument::positional).collect();
1091+
1092+
Ok(Expression::ToolCall {
1093+
name: "->".to_string(),
1094+
args,
1095+
})
1096+
}
1097+
10321098
// Helper methods
10331099

10341100
fn is_at_end(&self) -> bool {

0 commit comments

Comments
 (0)