Просмотр исходного кода

add operator to comparsion (#1883)

Implement operator <, <=, > and >=
pmqtt 3 лет назад
Родитель
Сommit
63da070671

+ 4 - 0
common/fuzzing/carbon.proto

@@ -60,6 +60,10 @@ message OperatorExpression {
     BitwiseXor = 17;
     BitShiftLeft = 18;
     BitShiftRight = 19;
+    Less = 20;
+    LessEq = 21;
+    Greater = 22;
+    GreaterEq = 23;
   }
   optional Operator op = 1;
   repeated Expression arguments = 2;

+ 12 - 0
common/fuzzing/proto_to_carbon.cpp

@@ -149,6 +149,18 @@ static auto OperatorToCarbon(const Fuzzing::OperatorExpression& operator_expr,
     case Fuzzing::OperatorExpression::Eq:
       BinaryOperatorToCarbon(arg0, " == ", arg1, out);
       break;
+    case Fuzzing::OperatorExpression::Less:
+      BinaryOperatorToCarbon(arg0, " < ", arg1, out);
+      break;
+    case Fuzzing::OperatorExpression::LessEq:
+      BinaryOperatorToCarbon(arg0, " <= ", arg1, out);
+      break;
+    case Fuzzing::OperatorExpression::GreaterEq:
+      BinaryOperatorToCarbon(arg0, " >= ", arg1, out);
+      break;
+    case Fuzzing::OperatorExpression::Greater:
+      BinaryOperatorToCarbon(arg0, " > ", arg1, out);
+      break;
 
     case Fuzzing::OperatorExpression::Or:
       BinaryOperatorToCarbon(arg0, " or ", arg1, out);

+ 15 - 1
explorer/ast/expression.cpp

@@ -32,13 +32,15 @@ auto IntrinsicExpression::FindIntrinsic(std::string_view name,
        {"delete", Intrinsic::Dealloc},
        {"rand", Intrinsic::Rand},
        {"int_eq", Intrinsic::IntEq},
+       {"int_compare", Intrinsic::IntCompare},
        {"int_bit_complement", Intrinsic::IntBitComplement},
        {"int_bit_and", Intrinsic::IntBitAnd},
        {"int_bit_or", Intrinsic::IntBitOr},
        {"int_bit_xor", Intrinsic::IntBitXor},
        {"int_left_shift", Intrinsic::IntLeftShift},
        {"int_right_shift", Intrinsic::IntRightShift},
-       {"str_eq", Intrinsic::StrEq}});
+       {"str_eq", Intrinsic::StrEq},
+       {"str_compare", Intrinsic::StrCompare}});
   name.remove_prefix(std::strlen("__intrinsic_"));
   auto it = intrinsic_map.find(name);
   if (it == intrinsic_map.end()) {
@@ -60,6 +62,8 @@ auto IntrinsicExpression::name() const -> std::string_view {
       return "__intrinsic_rand";
     case IntrinsicExpression::Intrinsic::IntEq:
       return "__intrinsic_int_eq";
+    case IntrinsicExpression::Intrinsic::IntCompare:
+      return "__intrinsic_int_compare";
     case IntrinsicExpression::Intrinsic::IntBitComplement:
       return "__intrinsic_int_bit_complement";
     case IntrinsicExpression::Intrinsic::IntBitAnd:
@@ -74,6 +78,8 @@ auto IntrinsicExpression::name() const -> std::string_view {
       return "__intrinsic_int_right_shift";
     case IntrinsicExpression::Intrinsic::StrEq:
       return "__intrinsic_str_eq";
+    case IntrinsicExpression::Intrinsic::StrCompare:
+      return "__intrinsic_str_compare";
   }
 }
 
@@ -131,6 +137,14 @@ auto ToString(Operator op) -> std::string_view {
       return "==";
     case Operator::Mod:
       return "%";
+    case Operator::Less:
+      return "<";
+    case Operator::LessEq:
+      return "<=";
+    case Operator::Greater:
+      return ">";
+    case Operator::GreaterEq:
+      return ">=";
   }
 }
 

+ 6 - 0
explorer/ast/expression.h

@@ -124,6 +124,10 @@ enum class Operator {
   Complement,
   Deref,
   Eq,
+  Less,
+  LessEq,
+  Greater,
+  GreaterEq,
   Mul,
   Mod,
   Neg,
@@ -676,6 +680,8 @@ class IntrinsicExpression : public Expression {
     Rand,
     IntEq,
     StrEq,
+    StrCompare,
+    IntCompare,
     IntBitAnd,
     IntBitOr,
     IntBitXor,

+ 178 - 0
explorer/data/prelude.carbon

@@ -57,6 +57,10 @@ impl forall [U1:! Type, U2:! Type, U3:! Type,
 // Comparison interfaces.
 // ----------------------
 
+// ----------------------
+// EQUAL
+// ----------------------
+
 interface EqWith(U:! Type) {
   fn Equal[me: Self](other: U) -> bool;
   // TODO: NotEqual with default impl
@@ -86,6 +90,180 @@ impl String as EqWith(Self) {
   }
 }
 
+// ----------------------
+// COMPARE
+// ----------------------
+
+choice Ordering {
+  Less,
+  Equivalent,
+  Greater,
+  Incomparable
+}
+
+interface CompareWith(U:! Type) {
+  fn Compare[me: Self](u: U) -> Ordering;
+  // TODO: Add `default fn` for Less, LessOrEquivalent, Greater, and GreaterOrEquivalent once it's available.
+}
+// TODO: constraint Ordered { ... }
+
+impl i32 as CompareWith(Self) {
+  fn Compare[me: Self](other: Self) -> Ordering {
+    var comp: i32 = __intrinsic_int_compare(me, other);
+    if (comp == -1) {
+      return Ordering.Less();
+    }
+    if (comp == 0) {
+      return Ordering.Equivalent();
+    }
+    if (comp == 1) {
+      return Ordering.Greater();
+    }
+    return Ordering.Incomparable();
+
+  }
+}
+
+impl String as CompareWith(Self) {
+  fn Compare[me: Self](other: Self) -> Ordering {
+    var comp: i32 = __intrinsic_str_compare(me, other);
+    if (comp == -1) {
+      return Ordering.Less();
+    }
+    if (comp == 0) {
+      return Ordering.Equivalent();
+    }
+    if (comp == 1) {
+      return Ordering.Greater();
+    }
+    return Ordering.Incomparable();
+  }
+}
+
+interface LessWith(U:! Type) {
+  fn Less[me: Self](other: U) -> Bool;
+}
+
+interface LessEqWith(U:! Type) {
+  fn LessEq[me: Self](other: U) -> Bool;
+}
+
+interface GreaterWith(U:! Type) {
+  fn Greater[me: Self](other: U) -> Bool;
+}
+
+interface GreaterEqWith(U:! Type) {
+  fn GreaterEq[me: Self](other: U) -> Bool;
+}
+
+impl i32 as LessWith(Self) {
+  fn Less[me: Self](other: Self) -> Bool {
+    var comp: Ordering = me.(CompareWith(i32).Compare)(other);
+    match (comp) {
+      case Ordering.Less() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl String as LessWith(Self) {
+  fn Less[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(String).Compare)(other);
+    match(comp){
+      case Ordering.Less() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl i32 as LessEqWith(Self) {
+  fn LessEq[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(i32).Compare)(other);
+    match(comp){
+      case Ordering.Less() => {
+        return true;
+      }
+      case Ordering.Equivalent() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl String as LessEqWith(Self) {
+  fn LessEq[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(String).Compare)(other);
+    match(comp){
+      case Ordering.Less() => {
+        return true;
+      }
+      case Ordering.Equivalent() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl i32 as GreaterWith(Self) {
+  fn Greater[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(i32).Compare)(other);
+    match(comp){
+      case Ordering.Greater() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl String as GreaterWith(Self) {
+  fn Greater[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(String).Compare)(other);
+    match(comp){
+      case Ordering.Greater() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl i32 as GreaterEqWith(Self) {
+  fn GreaterEq[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(i32).Compare)(other);
+    match(comp){
+      case Ordering.Greater() => {
+        return true;
+      }
+      case Ordering.Equivalent() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+impl String as GreaterEqWith(Self) {
+  fn GreaterEq[me: Self](other: Self) -> Bool {
+    var comp: Ordering =  me.(CompareWith(String).Compare)(other);
+    match(comp){
+      case Ordering.Greater() => {
+        return true;
+      }
+      case Ordering.Equivalent() => {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
 // ----------------------
 // Arithmetic interfaces.
 // ----------------------

+ 8 - 0
explorer/fuzzing/ast_to_proto.cpp

@@ -53,6 +53,14 @@ static auto OperatorToProtoEnum(const Operator op)
       return Fuzzing::OperatorExpression::And;
     case Operator::Eq:
       return Fuzzing::OperatorExpression::Eq;
+    case Operator::Less:
+      return Fuzzing::OperatorExpression::Less;
+    case Operator::LessEq:
+      return Fuzzing::OperatorExpression::LessEq;
+    case Operator::Greater:
+      return Fuzzing::OperatorExpression::Greater;
+    case Operator::GreaterEq:
+      return Fuzzing::OperatorExpression::GreaterEq;
     case Operator::Mul:
       return Fuzzing::OperatorExpression::Mul;
     case Operator::Mod:

+ 15 - 3
explorer/interpreter/builtins.h

@@ -29,6 +29,11 @@ class Builtins {
 
     // Comparison.
     EqWith,
+    LessWith,
+    LessEqWith,
+    GreaterWith,
+    GreaterEqWith,
+    CompareWith,
 
     // Arithmetic.
     Negate,
@@ -51,6 +56,10 @@ class Builtins {
   static constexpr Builtin As = Builtin::As;
   static constexpr Builtin ImplicitAs = Builtin::ImplicitAs;
   static constexpr Builtin EqWith = Builtin::EqWith;
+  static constexpr Builtin LessWith = Builtin::LessWith;
+  static constexpr Builtin LessEqWith = Builtin::LessEqWith;
+  static constexpr Builtin GreaterWith = Builtin::GreaterWith;
+  static constexpr Builtin GreaterEqWith = Builtin::GreaterEqWith;
   static constexpr Builtin Negate = Builtin::Negate;
   static constexpr Builtin AddWith = Builtin::AddWith;
   static constexpr Builtin SubWith = Builtin::SubWith;
@@ -62,6 +71,7 @@ class Builtins {
   static constexpr Builtin BitXorWith = Builtin::BitXorWith;
   static constexpr Builtin LeftShiftWith = Builtin::LeftShiftWith;
   static constexpr Builtin RightShiftWith = Builtin::RightShiftWith;
+  static constexpr Builtin CompareWith = Builtin::CompareWith;
 
   // Register a declaration that might be a builtin.
   void Register(Nonnull<const Declaration*> decl);
@@ -78,9 +88,11 @@ class Builtins {
  private:
   static constexpr int NumBuiltins = static_cast<int>(Builtin::Last) + 1;
   static constexpr const char* BuiltinNames[NumBuiltins] = {
-      "As",        "ImplicitAs", "EqWith",        "Negate",        "AddWith",
-      "SubWith",   "MulWith",    "ModWith",       "BitComplement", "BitAndWith",
-      "BitOrWith", "BitXorWith", "LeftShiftWith", "RightShiftWith"};
+      "As",         "ImplicitAs",    "EqWith",        "LessWith",
+      "LessEqWith", "GreaterWith",   "GreaterEqWith", "CompareWith",
+      "Negate",     "AddWith",       "SubWith",       "MulWith",
+      "ModWith",    "BitComplement", "BitAndWith",    "BitOrWith",
+      "BitXorWith", "LeftShiftWith", "RightShiftWith"};
 
   std::optional<Nonnull<const Declaration*>> builtins_[NumBuiltins] = {};
 };

+ 34 - 0
explorer/interpreter/interpreter.cpp

@@ -214,6 +214,10 @@ auto Interpreter::EvalPrim(Operator op, Nonnull<const Value*> static_type,
       return &cast<TypeOfConstraintType>(static_type)->constraint_type();
     case Operator::As:
     case Operator::Eq:
+    case Operator::Less:
+    case Operator::LessEq:
+    case Operator::Greater:
+    case Operator::GreaterEq:
     case Operator::BitwiseOr:
     case Operator::BitwiseXor:
     case Operator::BitShiftLeft:
@@ -1227,6 +1231,36 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
           auto result = arena_->New<BoolValue>(lhs == rhs);
           return todo_.FinishAction(result);
         }
+        case IntrinsicExpression::Intrinsic::IntCompare: {
+          CARBON_CHECK(args.size() == 2);
+          auto lhs = cast<IntValue>(*args[0]).value();
+          auto rhs = cast<IntValue>(*args[1]).value();
+          if (lhs < rhs) {
+            auto result = arena_->New<IntValue>(-1);
+            return todo_.FinishAction(result);
+          }
+          if (lhs == rhs) {
+            auto result = arena_->New<IntValue>(0);
+            return todo_.FinishAction(result);
+          }
+          auto result = arena_->New<IntValue>(1);
+          return todo_.FinishAction(result);
+        }
+        case IntrinsicExpression::Intrinsic::StrCompare: {
+          CARBON_CHECK(args.size() == 2);
+          auto& lhs = cast<StringValue>(*args[0]).value();
+          auto& rhs = cast<StringValue>(*args[1]).value();
+          if (lhs < rhs) {
+            auto result = arena_->New<IntValue>(-1);
+            return todo_.FinishAction(result);
+          }
+          if (lhs == rhs) {
+            auto result = arena_->New<IntValue>(0);
+            return todo_.FinishAction(result);
+          }
+          auto result = arena_->New<IntValue>(1);
+          return todo_.FinishAction(result);
+        }
         case IntrinsicExpression::Intrinsic::IntBitComplement: {
           CARBON_CHECK(args.size() == 1);
           return todo_.FinishAction(

+ 86 - 0
explorer/interpreter/type_checker.cpp

@@ -2086,6 +2086,62 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           op.set_rewritten_form(*converted);
           return Success();
         }
+        case Operator::Less: {
+          ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
+              impl_scope, op.arguments()[0],
+              BuiltinInterfaceName{Builtins::LessWith, ts[1]},
+              BuiltinMethodCall{"Less", op.arguments()[1]});
+          if (!converted.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << *ts[0] << " is not less comparable with " << *ts[1]
+                   << " (" << converted.error().message() << ")";
+          }
+          op.set_rewritten_form(*converted);
+          return Success();
+        }
+        case Operator::LessEq: {
+          ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
+              impl_scope, op.arguments()[0],
+              BuiltinInterfaceName{Builtins::LessEqWith, ts[1]},
+              BuiltinMethodCall{"LessEq", op.arguments()[1]});
+          if (!converted.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << *ts[0] << " is not less equal comparable with " << *ts[1]
+                   << " (" << converted.error().message() << ")";
+          }
+          op.set_rewritten_form(*converted);
+          return Success();
+        }
+        case Operator::GreaterEq: {
+          ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
+              impl_scope, op.arguments()[0],
+              BuiltinInterfaceName{Builtins::GreaterEqWith, ts[1]},
+              BuiltinMethodCall{"GreaterEq", op.arguments()[1]});
+          if (!converted.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << *ts[0] << " is not greater equal comparable with "
+                   << *ts[1] << " (" << converted.error().message() << ")";
+          }
+          op.set_rewritten_form(*converted);
+          return Success();
+        }
+        case Operator::Greater: {
+          ErrorOr<Nonnull<Expression*>> converted = BuildBuiltinMethodCall(
+              impl_scope, op.arguments()[0],
+              BuiltinInterfaceName{Builtins::GreaterWith, ts[1]},
+              BuiltinMethodCall{"Greater", op.arguments()[1]});
+          if (!converted.ok()) {
+            // We couldn't find a matching `impl`.
+            return CompilationError(e->source_loc())
+                   << *ts[0] << " is not greater comparable with " << *ts[1]
+                   << " (" << converted.error().message() << ")";
+          }
+          op.set_rewritten_form(*converted);
+          return Success();
+        }
         case Operator::Deref:
           CARBON_RETURN_IF_ERROR(
               ExpectPointerType(e->source_loc(), "*", ts[0]));
@@ -2311,6 +2367,21 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
+        case IntrinsicExpression::Intrinsic::IntCompare: {
+          if (args.size() != 2) {
+            return CompilationError(e->source_loc())
+                   << "__intrinsic_int_compare takes 2 arguments";
+          }
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "__intrinsic_int_compare argument 1",
+              arena_->New<IntType>(), &args[0]->static_type(), impl_scope));
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "__intrinsic_int_compare argument 2",
+              arena_->New<IntType>(), &args[1]->static_type(), impl_scope));
+          e->set_static_type(arena_->New<IntType>());
+          e->set_value_category(ValueCategory::Let);
+          return Success();
+        }
         case IntrinsicExpression::Intrinsic::StrEq: {
           if (args.size() != 2) {
             return CompilationError(e->source_loc())
@@ -2326,6 +2397,21 @@ auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
           e->set_value_category(ValueCategory::Let);
           return Success();
         }
+        case IntrinsicExpression::Intrinsic::StrCompare: {
+          if (args.size() != 2) {
+            return CompilationError(e->source_loc())
+                   << "__intrinsic_str_compare takes 2 arguments";
+          }
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "__intrinsic_str_compare argument 1",
+              arena_->New<StringType>(), &args[0]->static_type(), impl_scope));
+          CARBON_RETURN_IF_ERROR(ExpectExactType(
+              e->source_loc(), "__intrinsic_str_compare argument 2",
+              arena_->New<StringType>(), &args[1]->static_type(), impl_scope));
+          e->set_static_type(arena_->New<IntType>());
+          e->set_value_category(ValueCategory::Let);
+          return Success();
+        }
         case IntrinsicExpression::Intrinsic::IntBitComplement:
           if (args.size() != 1) {
             return CompilationError(e->source_loc())

+ 8 - 0
explorer/syntax/lexer.lpp

@@ -65,6 +65,8 @@ FALSE                "false"
 FN                   "fn"
 FN_TYPE              "__Fn"
 FORALL               "forall"
+GREATER              ">"
+GREATER_EQUAL        ">="
 GREATER_GREATER      ">>"
 IF                   "if"
 IMPL                 "impl"
@@ -74,6 +76,8 @@ IS                   "is"
 LEFT_CURLY_BRACE     "{"
 LEFT_PARENTHESIS     "("
 LEFT_SQUARE_BRACKET  "["
+LESS                 "<"
+LESS_EQUAL           "<="
 LESS_LESS            "<<"
 LET                  "let"
 LIBRARY              "library"
@@ -161,7 +165,9 @@ operand_start         [(A-Za-z0-9_\"]
 {FN_TYPE}             { return CARBON_SIMPLE_TOKEN(FN_TYPE);             }
 {FN}                  { return CARBON_SIMPLE_TOKEN(FN);                  }
 {FORALL}              { return CARBON_SIMPLE_TOKEN(FORALL);              }
+{GREATER_EQUAL}       { return CARBON_SIMPLE_TOKEN(GREATER_EQUAL);       }
 {GREATER_GREATER}     { return CARBON_SIMPLE_TOKEN(GREATER_GREATER);     }
+{GREATER}             { return CARBON_SIMPLE_TOKEN(GREATER);             }
 {IF}                  { return CARBON_SIMPLE_TOKEN(IF);                  }
 {IMPL}                { return CARBON_SIMPLE_TOKEN(IMPL);                }
 {IMPORT}              { return CARBON_SIMPLE_TOKEN(IMPORT);              }
@@ -170,7 +176,9 @@ operand_start         [(A-Za-z0-9_\"]
 {LEFT_CURLY_BRACE}    { return CARBON_SIMPLE_TOKEN(LEFT_CURLY_BRACE);    }
 {LEFT_PARENTHESIS}    { return CARBON_SIMPLE_TOKEN(LEFT_PARENTHESIS);    }
 {LEFT_SQUARE_BRACKET} { return CARBON_SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); }
+{LESS_EQUAL}          { return CARBON_SIMPLE_TOKEN(LESS_EQUAL);          }
 {LESS_LESS}           { return CARBON_SIMPLE_TOKEN(LESS_LESS);           }
+{LESS}                { return CARBON_SIMPLE_TOKEN(LESS);                }
 {LET}                 { return CARBON_SIMPLE_TOKEN(LET);                 }
 {LIBRARY}             { return CARBON_SIMPLE_TOKEN(LIBRARY);             }
 {MATCH}               { return CARBON_SIMPLE_TOKEN(MATCH);               }

+ 20 - 2
explorer/syntax/parser.ypp

@@ -190,6 +190,7 @@
 %type <std::vector<Nonnull<AlternativeSignature*>>> alternative_list_contents
 %type <BisonWrap<Match::Clause>> clause
 %type <std::vector<Match::Clause>> clause_list
+%type <Operator> comparison_operator;
 
 %token
   // Most tokens have their spelling defined in lexer.lpp.
@@ -228,6 +229,8 @@
   FN
   FN_TYPE
   FORALL
+  GREATER
+  GREATER_EQUAL
   GREATER_GREATER
   IF
   IMPL
@@ -237,6 +240,8 @@
   LEFT_CURLY_BRACE
   LEFT_PARENTHESIS
   LEFT_SQUARE_BRACKET
+  LESS
+  LESS_EQUAL
   LESS_LESS
   LET
   LIBRARY
@@ -584,12 +589,25 @@ comparison_operand:
   ref_deref_expression
 | value_expression
 ;
+/* TODO: Add NOT_EQUAL */
+comparison_operator:
+  EQUAL_EQUAL
+    { $$ = Operator::Eq; }
+| LESS
+    { $$ = Operator::Less; }
+| LESS_EQUAL
+    { $$ = Operator::LessEq; }
+|  GREATER
+    { $$ = Operator::Greater; }
+| GREATER_EQUAL
+    { $$ = Operator::GreaterEq; }
+;
 comparison_expression:
   value_expression
-| comparison_operand EQUAL_EQUAL comparison_operand
+| comparison_operand comparison_operator comparison_operand
     {
       $$ = arena->New<OperatorExpression>(
-          context.source_loc(), Operator::Eq,
+          context.source_loc(), $2,
           std::vector<Nonnull<Expression*>>({$1, $3}));
     }
 ;

+ 32 - 0
explorer/testdata/comparison/builtin_comparsion.carbon

@@ -0,0 +1,32 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: strings less: 1
+// CHECK: ints less: 0
+// CHECK: strings less eq: 1
+// CHECK: ints less eq: 1
+// CHECK: strings greater: 0
+// CHECK: ints greater: 0
+// CHECK: strings greater eq: 0
+// CHECK: ints greater eq: 1
+// CHECK: result: 0
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  Print("strings less: {0}", if "hello" < "world" then 1 else 0);
+  Print("ints less: {0}", if 1 < 1 then 1 else 0);
+  Print("strings less eq: {0}", if "hello" <= "world" then 1 else 0);
+  Print("ints less eq: {0}", if 1 <= 1 then 1 else 0);
+  Print("strings greater: {0}", if "hello" > "world" then 1 else 0);
+  Print("ints greater: {0}", if 1 > 1 then 1 else 0);
+  Print("strings greater eq: {0}", if "hello" >= "world" then 1 else 0);
+  Print("ints greater eq: {0}", if 1 >= 1 then 1 else 0);
+  return 0;
+}