diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 27fa62534..09a51d99b 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -5,7 +5,7 @@ use crate::{ }; use hir::{ ArenaId, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, - Name, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, + Name, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, UnaryOp, }; use inkwell::{ builder::Builder, @@ -206,6 +206,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { Expr::BinaryOp { lhs, rhs, op } => { self.gen_binary_op(expr, *lhs, *rhs, op.expect("missing op")) } + Expr::UnaryOp { expr, op } => self.gen_unary_op(*expr, *op), Expr::Call { ref callee, ref args, @@ -624,6 +625,68 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { } } + /// Generates IR to calculate a unary operation on an expression. + fn gen_unary_op(&mut self, expr: ExprId, op: UnaryOp) -> Option { + let ty = self.infer[expr].clone(); + match ty.as_simple() { + Some(TypeCtor::Float(_ty)) => self.gen_unary_op_float(expr, op), + Some(TypeCtor::Int(ty)) => self.gen_unary_op_int(expr, op, ty.signedness), + Some(TypeCtor::Bool) => self.gen_unary_op_bool(expr, op), + _ => unimplemented!("unimplemented operation op{0}", ty.display(self.db)), + } + } + + /// Generates IR to calculate a unary operation on a floating point value. + fn gen_unary_op_float(&mut self, expr: ExprId, op: UnaryOp) -> Option { + let value: FloatValue = self + .gen_expr(expr) + .map(|value| self.opt_deref_value(self.infer[expr].clone(), value)) + .expect("no value") + .into_float_value(); + match op { + UnaryOp::Neg => Some(self.builder.build_float_neg(value, "neg").into()), + _ => unimplemented!("Operator {:?} is not implemented for float", op), + } + } + + /// Generates IR to calculate a unary operation on an integer value. + fn gen_unary_op_int( + &mut self, + expr: ExprId, + op: UnaryOp, + signedness: hir::Signedness, + ) -> Option { + let value: IntValue = self + .gen_expr(expr) + .map(|value| self.opt_deref_value(self.infer[expr].clone(), value)) + .expect("no value") + .into_int_value(); + match op { + UnaryOp::Neg => { + if signedness == hir::Signedness::Signed { + Some(self.builder.build_int_neg(value, "neg").into()) + } else { + unimplemented!("Operator {:?} is not implemented for unsigned integer", op) + } + } + UnaryOp::Not => Some(self.builder.build_not(value, "not").into()), + //_ => unimplemented!("Operator {:?} is not implemented for integer", op), + } + } + + /// Generates IR to calculate a unary operation on a boolean value. + fn gen_unary_op_bool(&mut self, expr: ExprId, op: UnaryOp) -> Option { + let value: IntValue = self + .gen_expr(expr) + .map(|value| self.opt_deref_value(self.infer[expr].clone(), value)) + .expect("no value") + .into_int_value(); + match op { + UnaryOp::Not => Some(self.builder.build_not(value, "not").into()), + _ => unimplemented!("Operator {:?} is not implemented for boolean", op), + } + } + /// Generates IR to calculate a binary operation between two floating point values. fn gen_binary_op_float( &mut self, diff --git a/crates/mun_codegen/src/snapshots/test__unary_expressions.snap b/crates/mun_codegen/src/snapshots/test__unary_expressions.snap new file mode 100644 index 000000000..02aeeae37 --- /dev/null +++ b/crates/mun_codegen/src/snapshots/test__unary_expressions.snap @@ -0,0 +1,51 @@ +--- +source: crates/mun_codegen/src/test.rs +expression: "pub fn negf(x: float) -> float {\n -x\n}\n\npub fn negi(x: int) -> int {\n -x\n}\n\npub fn notb(x: bool) -> bool {\n !x\n}\n\npub fn noti(x: int) -> int {\n !x\n}" +--- +; == FILE IR ===================================== +; ModuleID = 'main.mun' +source_filename = "main.mun" + +%struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } + +@global_type_table = external global [3 x %struct.MunTypeInfo addrspace(4)*] + +define double @negf(double) { +body: + %neg = fsub double -0.000000e+00, %0 + ret double %neg +} + +define i64 @negi(i64) { +body: + %neg = sub i64 0, %0 + ret i64 %neg +} + +define i1 @notb(i1) { +body: + %not = xor i1 %0, true + ret i1 %not +} + +define i64 @noti(i64) { +body: + %not = xor i64 %0, -1 + ret i64 %not +} + + +; == GROUP IR ==================================== +; ModuleID = 'group_name' +source_filename = "group_name" + +%struct.MunTypeInfo = type { [16 x i8], i8 addrspace(4)*, i32, i8, i8 } + +@"type_info::::name" = private unnamed_addr constant [10 x i8] c"core::i64\00" +@"type_info::" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"G\13;t\97j8\18\D7M\83`\1D\C8\19%", [10 x i8]* @"type_info::::name", i32 64, i8 8, i8 0 } +@"type_info::::name" = private unnamed_addr constant [10 x i8] c"core::f64\00" +@"type_info::" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"`\DBF\9C?YJ%G\AD4\9F\D5\92%A", [10 x i8]* @"type_info::::name", i32 64, i8 8, i8 0 } +@"type_info::::name" = private unnamed_addr constant [11 x i8] c"core::bool\00" +@"type_info::" = private unnamed_addr constant %struct.MunTypeInfo { [16 x i8] c"x\82\81m t7\03\CB\F8k\81-;\C9\84", [11 x i8]* @"type_info::::name", i32 1, i8 1, i8 0 } +@global_type_table = global [3 x %struct.MunTypeInfo addrspace(4)*] [%struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::", %struct.MunTypeInfo addrspace(4)* @"type_info::"] + diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index 7fdb870b4..43d37733c 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -134,6 +134,29 @@ fn binary_expressions() { ); } +#[test] +fn unary_expressions() { + test_snapshot( + r#" + pub fn negf(x: float) -> float { + -x + } + + pub fn negi(x: int) -> int { + -x + } + + pub fn notb(x: bool) -> bool { + !x + } + + pub fn noti(x: int) -> int { + !x + } + "#, + ); +} + #[test] fn let_statement() { test_snapshot( diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index b1b078f54..2eaa7a8c6 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -276,6 +276,27 @@ impl Diagnostic for CannotApplyBinaryOp { } } +#[derive(Debug)] +pub struct CannotApplyUnaryOp { + pub file: FileId, + pub expr: SyntaxNodePtr, + pub ty: Ty, +} + +impl Diagnostic for CannotApplyUnaryOp { + fn message(&self) -> String { + "cannot apply unary operator".to_string() + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + #[derive(Debug)] pub struct DuplicateDefinition { pub file: FileId, diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 2ed324d7e..b5ffe0547 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -49,7 +49,7 @@ pub use crate::{ display::HirDisplay, expr::{ resolver_for_expr, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, ExprScopes, Literal, - LogicOp, Ordering, Pat, PatId, RecordLitField, Statement, + LogicOp, Ordering, Pat, PatId, RecordLitField, Statement, UnaryOp, }, ids::ItemLoc, input::{FileId, SourceRoot, SourceRootId}, diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index 4aa4e6561..b567b984e 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -4,7 +4,7 @@ use crate::{ code_model::{DefWithBody, DefWithStruct, Struct}, diagnostics::DiagnosticSink, expr, - expr::{Body, Expr, ExprId, Literal, Pat, PatId, RecordLitField, Statement}, + expr::{Body, Expr, ExprId, Literal, Pat, PatId, RecordLitField, Statement, UnaryOp}, name_resolution::Namespace, resolve::{Resolution, Resolver}, ty::infer::diagnostics::InferenceDiagnostic, @@ -423,9 +423,38 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { } } } - Expr::UnaryOp { .. } => Ty::Unknown, - // Expr::UnaryOp { expr: _, op: _ } => {} - // Expr::Block { statements: _, tail: _ } => {} + Expr::UnaryOp { expr, op } => { + let ty = + self.infer_expr_inner(*expr, &Expectation::none(), &CheckParams::default()); + if let Some(simple) = ty.as_simple() { + match op { + UnaryOp::Not => { + match simple { + TypeCtor::Bool | TypeCtor::Int(_) => ty, + _ => { + self.diagnostics.push( + InferenceDiagnostic::CannotApplyUnaryOp { id: *expr, ty }, + ); + Ty::Unknown + } + } + } + UnaryOp::Neg => { + match simple { + TypeCtor::Float(_) | TypeCtor::Int(_) => ty, + _ => { + self.diagnostics.push( + InferenceDiagnostic::CannotApplyUnaryOp { id: *expr, ty }, + ); + Ty::Unknown + } + } + } + } + } else { + Ty::Unknown + } + } // Expr::Block { statements: _, tail: _ } => {} }; self.set_expr_type(tgt_expr, ty.clone()); @@ -923,9 +952,9 @@ impl From for ExprOrPatId { mod diagnostics { use crate::diagnostics::{ AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, - ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS, LiteralOutOfRange, - MismatchedStructLit, MismatchedType, MissingElseBranch, MissingFields, NoFields, - NoSuchField, ParameterCountMismatch, ReturnMissingExpression, + CannotApplyUnaryOp, ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS, + LiteralOutOfRange, MismatchedStructLit, MismatchedType, MissingElseBranch, MissingFields, + NoFields, NoSuchField, ParameterCountMismatch, ReturnMissingExpression, }; use crate::{ adt::StructKind, @@ -972,6 +1001,10 @@ mod diagnostics { lhs: Ty, rhs: Ty, }, + CannotApplyUnaryOp { + id: ExprId, + ty: Ty, + }, InvalidLHS { id: ExprId, lhs: ExprId, @@ -1134,6 +1167,18 @@ mod diagnostics { rhs: rhs.clone(), }); } + InferenceDiagnostic::CannotApplyUnaryOp { id, ty } => { + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(CannotApplyUnaryOp { + file, + expr, + ty: ty.clone(), + }); + } InferenceDiagnostic::InvalidLHS { id, lhs } => { let id = body .expr_syntax(*id) diff --git a/crates/mun_hir/src/ty/snapshots/tests__infer_unary_ops.snap b/crates/mun_hir/src/ty/snapshots/tests__infer_unary_ops.snap new file mode 100644 index 000000000..6401e78ea --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__infer_unary_ops.snap @@ -0,0 +1,15 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "fn foo(a: int, b: bool) {\n a = -a;\n b = !b;\n}" +--- +[7; 8) 'a': int +[15; 16) 'b': bool +[24; 51) '{ ... !b; }': nothing +[30; 31) 'a': int +[30; 36) 'a = -a': nothing +[34; 36) '-a': int +[35; 36) 'a': int +[42; 43) 'b': bool +[42; 48) 'b = !b': nothing +[46; 48) '!b': bool +[47; 48) 'b': bool diff --git a/crates/mun_hir/src/ty/snapshots/tests__invalid_unary_ops.snap b/crates/mun_hir/src/ty/snapshots/tests__invalid_unary_ops.snap new file mode 100644 index 000000000..0bb46c57e --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__invalid_unary_ops.snap @@ -0,0 +1,19 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "fn bar(a: float, b: bool) {\n a = !a; // mismatched type\n b = -b; // mismatched type\n}" +--- +[37; 38): cannot apply unary operator +[36; 38): mismatched type +[68; 69): cannot apply unary operator +[67; 69): mismatched type +[7; 8) 'a': float +[17; 18) 'b': bool +[26; 91) '{ ...type }': nothing +[32; 33) 'a': float +[32; 38) 'a = !a': nothing +[36; 38) '!a': {unknown} +[37; 38) 'a': float +[63; 64) 'b': bool +[63; 69) 'b = -b': nothing +[67; 69) '-b': {unknown} +[68; 69) 'b': bool diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index dec33274b..2dc5cd059 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -176,6 +176,30 @@ fn update_operators() { ) } +#[test] +fn infer_unary_ops() { + infer_snapshot( + r#" + fn foo(a: int, b: bool) { + a = -a; + b = !b; + } + "#, + ) +} + +#[test] +fn invalid_unary_ops() { + infer_snapshot( + r#" + fn bar(a: float, b: bool) { + a = !a; // mismatched type + b = -b; // mismatched type + } + "#, + ) +} + #[test] fn infer_loop() { infer_snapshot(