diff --git a/out b/out new file mode 100644 index 000000000..3abe39842 --- /dev/null +++ b/out @@ -0,0 +1,246 @@ +Program { + statements: [ + Struct( + Struct { + name: "Hello", + fields: [ + StructField { + ident: "x", + type_exp: Integer { + bits: 32, + signed: true, + }, + }, + StructField { + ident: "y", + type_exp: Integer { + bits: 32, + signed: true, + }, + }, + ], + }, + ), + Function( + Function { + name: "test", + params: [ + Parameter { + ident: "x", + type_exp: Other { + id: "Hello", + }, + }, + ], + body: [ + Return( + None, + ), + ], + return_type: None, + }, + ), + Function( + Function { + name: "works", + params: [ + Parameter { + ident: "x", + type_exp: Integer { + bits: 64, + signed: true, + }, + }, + ], + body: [ + Let { + name: "z", + value: Literal( + Integer { + value: "0", + bits: None, + signed: None, + }, + ), + value_type: None, + span: ( + 107, + 117, + ), + }, + If { + condition: BinaryOp( + Literal( + Integer { + value: "2", + bits: None, + signed: None, + }, + ), + Eq, + Variable { + name: Spanned { + span: ( + 130, + 131, + ), + value: "x", + }, + value_type: None, + }, + ), + body: [ + Mutate { + name: "z", + value: BinaryOp( + Variable { + name: Spanned { + span: ( + 146, + 147, + ), + value: "x", + }, + value_type: None, + }, + Mul, + Literal( + Integer { + value: "2", + bits: None, + signed: None, + }, + ), + ), + value_type: None, + span: ( + 142, + 152, + ), + }, + ], + else_body: Some( + [ + Mutate { + name: "z", + value: BinaryOp( + Variable { + name: Spanned { + span: ( + 178, + 179, + ), + value: "x", + }, + value_type: None, + }, + Mul, + Literal( + Integer { + value: "3", + bits: None, + signed: None, + }, + ), + ), + value_type: None, + span: ( + 174, + 184, + ), + }, + ], + ), + }, + Return( + Some( + Variable { + name: Spanned { + span: ( + 202, + 203, + ), + value: "z", + }, + value_type: None, + }, + ), + ), + ], + return_type: Some( + Integer { + bits: 64, + signed: true, + }, + ), + }, + ), + Function( + Function { + name: "main", + params: [], + body: [ + Let { + name: "y", + value: Literal( + Integer { + value: "2", + bits: None, + signed: None, + }, + ), + value_type: None, + span: ( + 231, + 241, + ), + }, + Let { + name: "z", + value: Variable { + name: Spanned { + span: ( + 254, + 255, + ), + value: "y", + }, + value_type: None, + }, + value_type: None, + span: ( + 246, + 256, + ), + }, + Return( + Some( + Call { + function: "works", + args: [ + Variable { + name: Spanned { + span: ( + 274, + 275, + ), + value: "z", + }, + value_type: None, + }, + ], + value_type: None, + }, + ), + ), + ], + return_type: Some( + Integer { + bits: 64, + signed: true, + }, + ), + }, + ), + ], +} diff --git a/src/main.rs b/src/main.rs index 32e1fefd8..2eb62cd93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,7 +110,7 @@ fn main() -> Result<()> { let lexer = Lexer::new(code.as_str()); let parser = grammar::ProgramParser::new(); let mut ast = parser.parse(lexer)?; - type_analysis::type_inference(&mut ast); + type_analysis::type_inference2(&mut ast); println!("{ast:#?}"); } Commands::Compile { diff --git a/src/type_analysis.rs b/src/type_analysis.rs index 5acebf7d7..07849ff64 100644 --- a/src/type_analysis.rs +++ b/src/type_analysis.rs @@ -10,14 +10,7 @@ struct Storage { functions: HashMap, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum TypeGuess { - /// The guess comes from a strong source: call args, return type, let binding. - Strong, - /// - Weak, -} - +// this works, but need to find a way to store the found info + handle literal integer types (or not?) pub fn type_inference2(ast: &mut ast::Program) { let mut storage = Storage::default(); @@ -42,6 +35,8 @@ pub fn type_inference2(ast: &mut ast::Program) { } } + dbg!(&storage); + for function in storage.functions.values() { let mut scope_vars: HashMap> = HashMap::new(); @@ -49,7 +44,9 @@ pub fn type_inference2(ast: &mut ast::Program) { scope_vars.insert(arg.ident.clone(), Some(arg.type_exp.clone())); } - type_inference_scope(&function.body, &scope_vars, function, &storage); + let (new_scope_vars, _) = + type_inference_scope(&function.body, &scope_vars, function, &storage); + dbg!(new_scope_vars); } } @@ -73,7 +70,7 @@ fn type_inference_scope( } => { new_vars.insert(name.clone()); - let exp_type = type_inference_expression(value, &scope_vars, storage); + let exp_type = type_inference_expression(value, &mut scope_vars, storage, None); if value_type.is_none() { scope_vars.insert(name.clone(), exp_type); @@ -94,7 +91,7 @@ fn type_inference_scope( panic!("undeclared variable"); } - let exp_type = type_inference_expression(value, &scope_vars, storage); + let exp_type = type_inference_expression(value, &mut scope_vars, storage, None); let var = scope_vars.get_mut(name).unwrap(); if var.is_none() { @@ -108,9 +105,45 @@ fn type_inference_scope( body, else_body, } => { - let cond_type = type_inference_expression(condition, &scope_vars, storage); - }, - Statement::Return(_) => todo!(), + type_inference_expression( + condition, + &mut scope_vars, + storage, + Some(TypeExp::Boolean), + ); + + let (new_scope_vars, new_vars) = + type_inference_scope(body, &scope_vars, func, storage); + + for (k, v) in new_scope_vars.into_iter() { + // not a new var within the scope (shadowing), so type info is valid + if scope_vars.contains_key(&k) && !new_vars.contains(&k) { + scope_vars.insert(k, v); + } + } + + if let Some(body) = else_body { + let (new_scope_vars, new_vars) = + type_inference_scope(body, &scope_vars, func, storage); + + for (k, v) in new_scope_vars.into_iter() { + // not a new var within the scope (shadowing), so type info is valid + if scope_vars.contains_key(&k) && !new_vars.contains(&k) { + scope_vars.insert(k, v); + } + } + } + } + Statement::Return(exp) => { + if let Some(exp) = exp { + type_inference_expression( + exp, + &mut scope_vars, + storage, + func.return_type.clone(), + ); + } + } Statement::Function(_) => unreachable!(), Statement::Struct(_) => unreachable!(), } @@ -121,8 +154,9 @@ fn type_inference_scope( fn type_inference_expression( exp: &Expression, - scope_vars: &HashMap>, + scope_vars: &mut HashMap>, storage: &Storage, + expected_type: Option, ) -> Option { match exp { Expression::Literal(lit) => { @@ -148,24 +182,44 @@ fn type_inference_expression( Expression::Variable { name, value_type: _, - } => scope_vars.get(&name.value).cloned().flatten(), + } => { + let var = scope_vars.get(&name.value).cloned().flatten(); + + if expected_type.is_some() { + if var.is_none() { + scope_vars.insert(name.value.clone(), expected_type.clone()); + expected_type + } else if expected_type.is_some() { + assert_eq!(var, expected_type, "type mismatch with variables"); + expected_type + } else { + var + } + } else { + var + } + } Expression::Call { function, - args: _, + args, value_type: _, - } => storage - .functions - .get(function) - .map(|x| &x.return_type) - .cloned() - .flatten(), + } => { + let func = storage.functions.get(function).cloned().unwrap(); + + for (i, arg) in args.iter().enumerate() { + let arg_type = func.params[i].type_exp.clone(); + // result is ignored, but need these to infer call arg types + type_inference_expression(arg, scope_vars, storage, Some(arg_type)); + } + + func.return_type + } Expression::BinaryOp(lhs, op, rhs) => match op { - ast::OpCode::Eq | ast::OpCode::Ne => { - Some(TypeExp::Boolean) - }, + ast::OpCode::Eq | ast::OpCode::Ne => Some(TypeExp::Boolean), _ => { - let lhs_type = type_inference_expression(lhs, scope_vars, storage); - let rhs_type = type_inference_expression(rhs, scope_vars, storage); + let lhs_type = + type_inference_expression(lhs, scope_vars, storage, expected_type.clone()); + let rhs_type = type_inference_expression(rhs, scope_vars, storage, expected_type); if lhs_type.is_some() && rhs_type.is_some() { assert_eq!(lhs_type, rhs_type, "types should match");