Repository: rust-analyzer/rust-analyzer
Pull request #451 (2019-01-07 19:15:49)

More type inference for more binary expressions
Implements more of https://github.com/rust-analyzer/rust-analyzer/issues/390. Just works for primitive (numeric) types for now.

Found an issue where `let x: Ty = expr;` doesn't actually propagate the type information unless `Ty` is primitive and numeric. I'll open an issue for this.


Patch:
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index bba8527b7f..7827e82c4d 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -527,9 +527,7 @@ struct InferenceContext<'a, D: HirDatabase> {
     return_ty: Ty,
 }
 
-// helper function that determines whether a binary operator
-// always returns a boolean
-fn is_boolean_operator(op: BinaryOp) -> bool {
+fn binary_op_return_ty(op: BinaryOp, rhs_ty: Ty) -> Ty {
     match op {
         BinaryOp::BooleanOr
         | BinaryOp::BooleanAnd
@@ -537,7 +535,70 @@ fn is_boolean_operator(op: BinaryOp) -> bool {
         | BinaryOp::LesserEqualTest
         | BinaryOp::GreaterEqualTest
         | BinaryOp::LesserTest
-        | BinaryOp::GreaterTest => true,
+        | BinaryOp::GreaterTest => Ty::Bool,
+        BinaryOp::Assignment
+        | BinaryOp::AddAssign
+        | BinaryOp::SubAssign
+        | BinaryOp::DivAssign
+        | BinaryOp::MulAssign
+        | BinaryOp::RemAssign
+        | BinaryOp::ShrAssign
+        | BinaryOp::ShlAssign
+        | BinaryOp::BitAndAssign
+        | BinaryOp::BitOrAssign
+        | BinaryOp::BitXorAssign => Ty::unit(),
+        BinaryOp::Addition
+        | BinaryOp::Subtraction
+        | BinaryOp::Multiplication
+        | BinaryOp::Division
+        | BinaryOp::Remainder
+        | BinaryOp::LeftShift
+        | BinaryOp::RightShift
+        | BinaryOp::BitwiseAnd
+        | BinaryOp::BitwiseOr
+        | BinaryOp::BitwiseXor => match rhs_ty {
+            Ty::Uint(..) | Ty::Int(..) | Ty::Float(..) => rhs_ty,
+            _ => Ty::Unknown,
+        },
+        BinaryOp::RangeRightOpen | BinaryOp::RangeRightClosed => Ty::Unknown,
+    }
+}
+
+fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty {
+    match op {
+        BinaryOp::BooleanAnd | BinaryOp::BooleanOr => Ty::Bool,
+        BinaryOp::Assignment | BinaryOp::EqualityTest => match lhs_ty {
+            Ty::Uint(..) | Ty::Int(..) | Ty::Float(..) | Ty::Str | Ty::Char | Ty::Bool => lhs_ty,
+            _ => Ty::Unknown,
+        },
+        BinaryOp::LesserEqualTest
+        | BinaryOp::GreaterEqualTest
+        | BinaryOp::LesserTest
+        | BinaryOp::GreaterTest
+        | BinaryOp::AddAssign
+        | BinaryOp::SubAssign
+        | BinaryOp::DivAssign
+        | BinaryOp::MulAssign
+        | BinaryOp::RemAssign
+        | BinaryOp::ShrAssign
+        | BinaryOp::ShlAssign
+        | BinaryOp::BitAndAssign
+        | BinaryOp::BitOrAssign
+        | BinaryOp::BitXorAssign
+        | BinaryOp::Addition
+        | BinaryOp::Subtraction
+        | BinaryOp::Multiplication
+        | BinaryOp::Division
+        | BinaryOp::Remainder
+        | BinaryOp::LeftShift
+        | BinaryOp::RightShift
+        | BinaryOp::BitwiseAnd
+        | BinaryOp::BitwiseOr
+        | BinaryOp::BitwiseXor => match lhs_ty {
+            Ty::Uint(..) | Ty::Int(..) | Ty::Float(..) => lhs_ty,
+            _ => Ty::Unknown,
+        },
+        _ => Ty::Unknown,
     }
 }
 
@@ -889,20 +950,20 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
             }
             Expr::BinaryOp { lhs, rhs, op } => match op {
                 Some(op) => {
-                    let subtype_expectation = match op {
+                    let lhs_expectation = match op {
                         BinaryOp::BooleanAnd | BinaryOp::BooleanOr => {
                             Expectation::has_type(Ty::Bool)
                         }
                         _ => Expectation::none(),
                     };
-                    let _lhs_ty = self.infer_expr(*lhs, &subtype_expectation)?;
-                    let _rhs_ty = self.infer_expr(*rhs, &subtype_expectation)?;
-
-                    if is_boolean_operator(*op) {
-                        Ty::Bool
-                    } else {
-                        Ty::Unknown
-                    }
+                    let lhs_ty = self.infer_expr(*lhs, &lhs_expectation)?;
+                    // TODO: find implementation of trait corresponding to operation
+                    // symbol and resolve associated `Output` type
+                    let rhs_expectation = binary_op_rhs_expectation(*op, lhs_ty);
+                    let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expectation))?;
+
+                    // TODO: similar as above, return ty is often associated trait type
+                    binary_op_return_ty(*op, rhs_ty)
                 }
                 _ => Ty::Unknown,
             },
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs
index 9df8ec6637..9ab59738f9 100644
--- a/crates/ra_syntax/src/ast.rs
+++ b/crates/ra_syntax/src/ast.rs
@@ -504,7 +504,52 @@ pub enum BinOp {
     LesserTest,
     /// The `>` operator for comparison
     GreaterTest,
-    // TODO: lots of others
+    /// The `+` operator for addition
+    Addition,
+    /// The `*` operator for multiplication
+    Multiplication,
+    /// The `-` operator for subtraction
+    Subtraction,
+    /// The `/` operator for division
+    Division,
+    /// The `%` operator for remainder after division
+    Remainder,
+    /// The `<<` operator for left shift
+    LeftShift,
+    /// The `>>` operator for right shift
+    RightShift,
+    /// The `^` operator for bitwise XOR
+    BitwiseXor,
+    /// The `|` operator for bitwise OR
+    BitwiseOr,
+    /// The `&` operator for bitwise AND
+    BitwiseAnd,
+    /// The `..` operator for right-open ranges
+    RangeRightOpen,
+    /// The `..=` operator for right-closed ranges
+    RangeRightClosed,
+    /// The `=` operator for assignment
+    Assignment,
+    /// The `+=` operator for assignment after additon
+    AddAssign,
+    /// The `/=` operator for assignment after division
+    DivAssign,
+    /// The `*=` operator for assignment after multiplication
+    MulAssign,
+    /// The `%=` operator for assignment after remainders
+    RemAssign,
+    /// The `>>=` operator for assignment after shifting right
+    ShrAssign,
+    /// The `<<=` operator for assignment after shifting left
+    ShlAssign,
+    /// The `-=` operator for assignment after subtraction
+    SubAssign,
+    /// The `|=` operator for assignment after bitwise OR
+    BitOrAssign,
+    /// The `&=` operator for assignment after bitwise AND
+    BitAndAssign,
+    /// The `^=` operator for assignment after bitwise XOR
+    BitXorAssign,
 }
 
 impl<'a> BinExpr<'a> {
@@ -519,6 +564,29 @@ impl<'a> BinExpr<'a> {
                 GTEQ => Some(BinOp::GreaterEqualTest),
                 L_ANGLE => Some(BinOp::LesserTest),
                 R_ANGLE => Some(BinOp::GreaterTest),
+                PLUS => Some(BinOp::Addition),
+                STAR => Some(BinOp::Multiplication),
+                MINUS => Some(BinOp::Subtraction),
+                SLASH => Some(BinOp::Division),
+                PERCENT => Some(BinOp::Remainder),
+                SHL => Some(BinOp::LeftShift),
+                SHR => Some(BinOp::RightShift),
+                CARET => Some(BinOp::BitwiseXor),
+                PIPE => Some(BinOp::BitwiseOr),
+                AMP => Some(BinOp::BitwiseAnd),
+                DOTDOT => Some(BinOp::RangeRightOpen),
+                DOTDOTEQ => Some(BinOp::RangeRightClosed),
+                EQ => Some(BinOp::Assignment),
+                PLUSEQ => Some(BinOp::AddAssign),
+                SLASHEQ => Some(BinOp::DivAssign),
+                STAREQ => Some(BinOp::MulAssign),
+                PERCENTEQ => Some(BinOp::RemAssign),
+                SHREQ => Some(BinOp::ShrAssign),
+                SHLEQ => Some(BinOp::ShlAssign),
+                MINUSEQ => Some(BinOp::SubAssign),
+                PIPEEQ => Some(BinOp::BitOrAssign),
+                AMPEQ => Some(BinOp::BitAndAssign),
+                CARETEQ => Some(BinOp::BitXorAssign),
                 _ => None,
             })
             .next()
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 3c640ed47e..d7505ea06d 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -49,6 +49,7 @@ Grammar(
         ["^=", "CARETEQ"],
         ["/=", "SLASHEQ"],
         ["*=", "STAREQ"],
+        ["%=", "PERCENTEQ"],
         ["&&", "AMPAMP"],
         ["||", "PIPEPIPE"],
         ["<<", "SHL"],
diff --git a/crates/ra_syntax/src/syntax_kinds/generated.rs b/crates/ra_syntax/src/syntax_kinds/generated.rs
index ef4588d939..830fac9f4b 100644
--- a/crates/ra_syntax/src/syntax_kinds/generated.rs
+++ b/crates/ra_syntax/src/syntax_kinds/generated.rs
@@ -58,6 +58,7 @@ pub enum SyntaxKind {
     CARETEQ,
     SLASHEQ,
     STAREQ,
+    PERCENTEQ,
     AMPAMP,
     PIPEPIPE,
     SHL,
@@ -319,6 +320,7 @@ impl SyntaxKind {
             CARETEQ => &SyntaxInfo { name: "CARETEQ" },
             SLASHEQ => &SyntaxInfo { name: "SLASHEQ" },
             STAREQ => &SyntaxInfo { name: "STAREQ" },
+            PERCENTEQ => &SyntaxInfo { name: "PERCENTEQ" },
             AMPAMP => &SyntaxInfo { name: "AMPAMP" },
             PIPEPIPE => &SyntaxInfo { name: "PIPEPIPE" },
             SHL => &SyntaxInfo { name: "SHL" },


Test patch:
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index e6c7e225b3..2749d740c4 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -157,7 +157,7 @@ impl S {
 }
 
 #[test]
-fn infer_boolean_op() {
+fn infer_binary_op() {
     check_inference(
         r#"
 fn f(x: bool) -> i32 {
@@ -168,15 +168,18 @@ fn test() {
     let x = a && b;
     let y = true || false;
     let z = x == y;
-    let h = CONST_1 <= CONST_2;
+    let minus_forty: isize = -40isize;
+    let h = minus_forty <= CONST_2;
     let c = f(z || y) + 5;
     let d = b;
-    let e = 3i32 && "hello world";
+    let g = minus_forty ^= i;
+    let ten: usize = 10;
+    let ten_is_eleven = ten == some_num;
 
-    10 < 3
+    ten < 3
 }
 "#,
-        "boolean_op.txt",
+        "binary_op.txt",
     );
 }
 
diff --git a/crates/ra_hir/src/ty/tests/data/binary_op.txt b/crates/ra_hir/src/ty/tests/data/binary_op.txt
new file mode 100644
index 0000000000..59c07ff434
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/binary_op.txt
@@ -0,0 +1,46 @@
+[6; 7) 'x': [unknown]
+[22; 34) '{     0i32 }': i32
+[28; 32) '0i32': i32
+[46; 342) '{     ... < 3 }': bool
+[56; 57) 'x': bool
+[60; 61) 'a': bool
+[60; 66) 'a && b': bool
+[65; 66) 'b': bool
+[76; 77) 'y': bool
+[80; 84) 'true': bool
+[80; 93) 'true || false': bool
+[88; 93) 'false': bool
+[103; 104) 'z': bool
+[107; 108) 'x': bool
+[107; 113) 'x == y': bool
+[112; 113) 'y': bool
+[123; 134) 'minus_forty': isize
+[144; 152) '-40isize': isize
+[145; 152) '40isize': [unknown]
+[162; 163) 'h': bool
+[166; 177) 'minus_forty': isize
+[166; 188) 'minus_...ONST_2': bool
+[181; 188) 'CONST_2': isize
+[198; 199) 'c': i32
+[202; 203) 'f': fn([unknown],) -> i32
+[202; 211) 'f(z || y)': i32
+[202; 215) 'f(z || y) + 5': i32
+[204; 205) 'z': bool
+[204; 210) 'z || y': bool
+[209; 210) 'y': bool
+[214; 215) '5': i32
+[225; 226) 'd': [unknown]
+[229; 230) 'b': [unknown]
+[240; 241) 'g': ()
+[244; 255) 'minus_forty': isize
+[244; 260) 'minus_...y ^= i': ()
+[259; 260) 'i': isize
+[270; 273) 'ten': usize
+[283; 285) '10': usize
+[295; 308) 'ten_is_eleven': bool
+[311; 314) 'ten': usize
+[311; 326) 'ten == some_num': bool
+[318; 326) 'some_num': usize
+[333; 336) 'ten': usize
+[333; 340) 'ten < 3': bool
+[339; 340) '3': usize
diff --git a/crates/ra_hir/src/ty/tests/data/boolean_op.txt b/crates/ra_hir/src/ty/tests/data/boolean_op.txt
deleted file mode 100644
index cce8d68fb4..0000000000
--- a/crates/ra_hir/src/ty/tests/data/boolean_op.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-[6; 7) 'x': [unknown]
-[22; 34) '{     0i32 }': i32
-[28; 32) '0i32': i32
-[46; 237) '{     ... < 3 }': bool
-[56; 57) 'x': bool
-[60; 61) 'a': bool
-[60; 66) 'a && b': bool
-[65; 66) 'b': bool
-[76; 77) 'y': bool
-[80; 84) 'true': bool
-[80; 93) 'true || false': bool
-[88; 93) 'false': bool
-[103; 104) 'z': bool
-[107; 108) 'x': bool
-[107; 113) 'x == y': bool
-[112; 113) 'y': bool
-[123; 124) 'h': bool
-[127; 134) 'CONST_1': [unknown]
-[127; 145) 'CONST_...ONST_2': bool
-[138; 145) 'CONST_2': [unknown]
-[155; 156) 'c': [unknown]
-[159; 172) 'f(z || y) + 5': [unknown]
-[182; 183) 'd': [unknown]
-[186; 187) 'b': [unknown]
-[197; 198) 'e': bool
-[201; 205) '3i32': bool
-[201; 222) '3i32 &...world"': bool
-[209; 222) '"hello world"': bool
-[229; 231) '10': [unknown]
-[229; 235) '10 < 3': bool
-[234; 235) '3': [unknown]