Skip to content

Commit 08305b2

Browse files
committed
A working spaceship operator
1 parent 002da76 commit 08305b2

File tree

22 files changed

+271
-83
lines changed

22 files changed

+271
-83
lines changed

compiler/rustc_ast/src/ast.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,10 @@ pub enum BinOpKind {
906906
Ge,
907907
/// The `>` operator (greater than)
908908
Gt,
909+
/// The `<=>` operator (Ord::cmp)
910+
Cmp,
911+
/// The `<==>` operator (PartialOrd::partial_cmp)
912+
CmpPartial,
909913
}
910914

911915
impl BinOpKind {
@@ -930,6 +934,8 @@ impl BinOpKind {
930934
Ne => "!=",
931935
Ge => ">=",
932936
Gt => ">",
937+
Cmp => "<=>",
938+
CmpPartial => "<==>",
933939
}
934940
}
935941

@@ -946,7 +952,7 @@ impl BinOpKind {
946952
BitAnd => ExprPrecedence::BitAnd,
947953
BitXor => ExprPrecedence::BitXor,
948954
BitOr => ExprPrecedence::BitOr,
949-
Lt | Gt | Le | Ge | Eq | Ne => ExprPrecedence::Compare,
955+
Lt | Gt | Le | Ge | Eq | Ne | Cmp | CmpPartial => ExprPrecedence::Compare,
950956
And => ExprPrecedence::LAnd,
951957
Or => ExprPrecedence::LOr,
952958
}
@@ -955,7 +961,7 @@ impl BinOpKind {
955961
pub fn fixity(&self) -> Fixity {
956962
use BinOpKind::*;
957963
match self {
958-
Eq | Ne | Lt | Le | Gt | Ge => Fixity::None,
964+
Eq | Ne | Lt | Le | Gt | Ge | Cmp | CmpPartial => Fixity::None,
959965
Add | Sub | Mul | Div | Rem | And | Or | BitXor | BitAnd | BitOr | Shl | Shr => {
960966
Fixity::Left
961967
}
@@ -965,7 +971,7 @@ impl BinOpKind {
965971
pub fn is_comparison(self) -> bool {
966972
use BinOpKind::*;
967973
match self {
968-
Eq | Ne | Lt | Le | Gt | Ge => true,
974+
Eq | Ne | Lt | Le | Gt | Ge | Cmp | CmpPartial => true,
969975
Add | Sub | Mul | Div | Rem | And | Or | BitXor | BitAnd | BitOr | Shl | Shr => false,
970976
}
971977
}

compiler/rustc_builtin_macros/src/deriving/cmp/ord.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use rustc_ast::MetaItem;
1+
use rustc_ast::ptr::P;
2+
use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability};
23
use rustc_expand::base::{Annotatable, ExtCtxt};
34
use rustc_span::{Ident, Span, sym};
45
use thin_vec::thin_vec;
@@ -42,7 +43,6 @@ pub(crate) fn expand_deriving_ord(
4243
pub(crate) fn cs_cmp(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> BlockOrExpr {
4344
let test_id = Ident::new(sym::cmp, span);
4445
let equal_path = cx.path_global(span, cx.std_path(&[sym::cmp, sym::Ordering, sym::Equal]));
45-
let cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
4646

4747
// Builds:
4848
//
@@ -63,8 +63,23 @@ pub(crate) fn cs_cmp(cx: &ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) ->
6363
let [other_expr] = &field.other_selflike_exprs[..] else {
6464
cx.dcx().span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
6565
};
66-
let args = thin_vec![field.self_expr.clone(), other_expr.clone()];
67-
cx.expr_call_global(field.span, cmp_path.clone(), args)
66+
let convert = |expr: &P<Expr>| {
67+
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = &expr.kind {
68+
if let ExprKind::Block(..) = &inner.kind {
69+
// `&{ x }` form: remove the `&`, add parens.
70+
cx.expr_paren(field.span, inner.clone())
71+
} else {
72+
// `&x` form: remove the `&`.
73+
inner.clone()
74+
}
75+
} else {
76+
cx.expr_deref(field.span, expr.clone())
77+
}
78+
};
79+
80+
let lhs = convert(&field.self_expr);
81+
let rhs = convert(&other_expr);
82+
cx.expr_binary(field.span, BinOpKind::Cmp, lhs, rhs)
6883
}
6984
CsFold::Combine(span, expr1, expr2) => {
7085
let eq_arm = cx.arm(span, cx.pat_path(span, equal_path.clone()), expr1);

compiler/rustc_hir/src/lang_items.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ language_item_table! {
277277
OrderingEnum, sym::Ordering, ordering_enum, Target::Enum, GenericRequirement::Exact(0);
278278
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
279279
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
280+
Ord, sym::Ord, ord_trait, Target::Trait, GenericRequirement::Exact(0);
280281
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;
281282

282283
// A number of panic-related lang items. The `panic` item corresponds to divide-by-zero and

compiler/rustc_hir_typeck/src/op.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use rustc_data_structures::packed::Pu128;
44
use rustc_errors::codes::*;
55
use rustc_errors::{Applicability, Diag, struct_span_code_err};
6+
use rustc_hir::LangItem;
67
use rustc_infer::traits::ObligationCauseCode;
78
use rustc_middle::ty::adjustment::{
89
Adjust, Adjustment, AllowTwoPhase, AutoBorrow, AutoBorrowMutability,
@@ -194,6 +195,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
194195
self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
195196
tcx.types.bool
196197
}
198+
199+
BinOpCategory::ThreeWayComparison => {
200+
// both LHS and RHS and result will have the same type
201+
self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
202+
tcx.ty_ordering_enum(Some(lhs_span))
203+
}
204+
205+
BinOpCategory::ThreeWayComparisonPartial => {
206+
// both LHS and RHS and result will have the same type
207+
self.demand_suptype(rhs_span, lhs_ty, rhs_ty);
208+
let ordering_ty = tcx.ty_ordering_enum(Some(lhs_span));
209+
Ty::new_lang_item(tcx, ordering_ty, LangItem::Option).unwrap()
210+
}
197211
}
198212
}
199213

@@ -1006,7 +1020,9 @@ fn lang_item_for_op(
10061020
| hir::BinOpKind::Eq
10071021
| hir::BinOpKind::Ne
10081022
| hir::BinOpKind::And
1009-
| hir::BinOpKind::Or => {
1023+
| hir::BinOpKind::Or
1024+
| hir::BinOpKind::Cmp
1025+
| hir::BinOpKind::CmpPartial => {
10101026
span_bug!(span, "impossible assignment operation: {}=", op.node.as_str())
10111027
}
10121028
}
@@ -1027,6 +1043,8 @@ fn lang_item_for_op(
10271043
hir::BinOpKind::Ge => (sym::ge, lang.partial_ord_trait()),
10281044
hir::BinOpKind::Gt => (sym::gt, lang.partial_ord_trait()),
10291045
hir::BinOpKind::Eq => (sym::eq, lang.eq_trait()),
1046+
hir::BinOpKind::Cmp => (sym::cmp, lang.ord_trait()),
1047+
hir::BinOpKind::CmpPartial => (sym::partial_cmp, lang.partial_ord_trait()),
10301048
hir::BinOpKind::Ne => (sym::ne, lang.eq_trait()),
10311049
hir::BinOpKind::And | hir::BinOpKind::Or => {
10321050
span_bug!(span, "&& and || are not overloadable")
@@ -1062,6 +1080,9 @@ enum BinOpCategory {
10621080
/// ==, !=, etc -- takes equal types, produces bools, except for simd,
10631081
/// which produce the input type
10641082
Comparison,
1083+
1084+
ThreeWayComparison,
1085+
ThreeWayComparisonPartial,
10651086
}
10661087

10671088
impl BinOpCategory {
@@ -1086,6 +1107,9 @@ impl BinOpCategory {
10861107
| hir::BinOpKind::Ge
10871108
| hir::BinOpKind::Gt => BinOpCategory::Comparison,
10881109

1110+
hir::BinOpKind::Cmp => BinOpCategory::ThreeWayComparison,
1111+
hir::BinOpKind::CmpPartial => BinOpCategory::ThreeWayComparisonPartial,
1112+
10891113
hir::BinOpKind::And | hir::BinOpKind::Or => BinOpCategory::Shortcircuit,
10901114
}
10911115
}
@@ -1157,7 +1181,9 @@ fn is_builtin_binop<'tcx>(lhs: Ty<'tcx>, rhs: Ty<'tcx>, op: hir::BinOp) -> bool
11571181
|| lhs.is_bool() && rhs.is_bool()
11581182
}
11591183

1160-
BinOpCategory::Comparison => {
1184+
BinOpCategory::Comparison
1185+
| BinOpCategory::ThreeWayComparison
1186+
| BinOpCategory::ThreeWayComparisonPartial => {
11611187
lhs.references_error() || rhs.references_error() || lhs.is_scalar() && rhs.is_scalar()
11621188
}
11631189
}

compiler/rustc_lint/src/unused.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults {
161161
| hir::BinOpKind::Le
162162
| hir::BinOpKind::Ne
163163
| hir::BinOpKind::Ge
164-
| hir::BinOpKind::Gt => Some("comparison"),
164+
| hir::BinOpKind::Gt
165+
| hir::BinOpKind::Cmp
166+
| hir::BinOpKind::CmpPartial => Some("comparison"),
165167
hir::BinOpKind::Add
166168
| hir::BinOpKind::Sub
167169
| hir::BinOpKind::Div

compiler/rustc_mir_build/src/thir/cx/expr.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,9 @@ fn bin_op(op: hir::BinOpKind) -> BinOp {
13291329
hir::BinOpKind::Ne => BinOp::Ne,
13301330
hir::BinOpKind::Ge => BinOp::Ge,
13311331
hir::BinOpKind::Gt => BinOp::Gt,
1332-
_ => bug!("no equivalent for ast binop {:?}", op),
1332+
hir::BinOpKind::Cmp => BinOp::Cmp,
1333+
hir::BinOpKind::And | hir::BinOpKind::Or | hir::BinOpKind::CmpPartial => {
1334+
bug!("no equivalent for ast binop {:?}", op)
1335+
}
13331336
}
13341337
}

compiler/rustc_mir_transform/src/validate.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,7 +1105,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
11051105
check_kinds!(
11061106
x,
11071107
"Cannot three-way compare non-integer type {:?}",
1108-
ty::Char | ty::Uint(..) | ty::Int(..)
1108+
ty::Char | ty::Uint(..) | ty::Int(..) | ty::Bool
11091109
)
11101110
}
11111111
}

compiler/rustc_parse/src/errors.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,6 @@ pub(crate) enum InvalidComparisonOperatorSub {
210210
invalid: String,
211211
correct: String,
212212
},
213-
#[label(parse_spaceship_operator_invalid)]
214-
Spaceship(#[primary_span] Span),
215213
}
216214

217215
#[derive(Diagnostic)]

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,16 @@ impl<'a> Parser<'a> {
154154
}
155155

156156
self.expected_token_types.insert(TokenType::Operator);
157-
while let Some(op) = self.check_assoc_op() {
157+
while let Some(mut op) = self.check_assoc_op() {
158+
// Look for `<=>`
159+
if op.node == AssocOp::Binary(BinOpKind::Le) && self.look_ahead(1, |t| *t == token::Gt) {
160+
//let sp = op.span.to(self.token.span);
161+
//op = source_map::respan(sp, AssocOp::Cmp);
162+
op.node = AssocOp::Binary(BinOpKind::Cmp);
163+
self.bump();
164+
}
165+
let op = op;
166+
158167
let lhs_span = self.interpolated_or_expr_span(&lhs);
159168
let cur_op_span = self.token.span;
160169
let restrictions = if op.node.is_assign_like() {
@@ -225,6 +234,7 @@ impl<'a> Parser<'a> {
225234
self.bump();
226235
}
227236

237+
/*
228238
// Look for C++'s `<=>` and recover
229239
if op.node == AssocOp::Binary(BinOpKind::Le)
230240
&& self.token == token::Gt
@@ -238,6 +248,7 @@ impl<'a> Parser<'a> {
238248
});
239249
self.bump();
240250
}
251+
*/
241252

242253
if self.prev_token == token::BinOp(token::Plus)
243254
&& self.token == token::BinOp(token::Plus)

library/core/src/cmp.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,7 @@ impl<T: Clone> Clone for Reverse<T> {
937937
/// [`PartialOrd`] and [`PartialEq`] to disagree.
938938
///
939939
/// [`cmp`]: Ord::cmp
940+
#[cfg_attr(not(bootstrap), lang = "Ord")]
940941
#[doc(alias = "<")]
941942
#[doc(alias = ">")]
942943
#[doc(alias = "<=")]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// MIR for `bar` after built
2+
3+
fn bar(_1: (i32, u32), _2: (i32, u32)) -> std::cmp::Ordering {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: std::cmp::Ordering;
7+
let mut _3: &(i32, u32);
8+
let mut _4: &(i32, u32);
9+
10+
bb0: {
11+
StorageLive(_3);
12+
_3 = &_1;
13+
StorageLive(_4);
14+
_4 = &_2;
15+
_0 = <(i32, u32) as Ord>::cmp(move _3, move _4) -> [return: bb1, unwind: bb2];
16+
}
17+
18+
bb1: {
19+
StorageDead(_4);
20+
StorageDead(_3);
21+
return;
22+
}
23+
24+
bb2 (cleanup): {
25+
resume;
26+
}
27+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// MIR for `foo` after built
2+
3+
fn foo(_1: i32, _2: i32) -> std::cmp::Ordering {
4+
debug a => _1;
5+
debug b => _2;
6+
let mut _0: std::cmp::Ordering;
7+
let mut _3: i32;
8+
let mut _4: i32;
9+
10+
bb0: {
11+
StorageLive(_3);
12+
_3 = copy _1;
13+
StorageLive(_4);
14+
_4 = copy _2;
15+
_0 = Cmp(move _3, move _4);
16+
StorageDead(_4);
17+
StorageDead(_3);
18+
return;
19+
}
20+
}

tests/mir-opt/building/spaceship.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ compile-flags: -C opt-level=0 -Z mir-opt-level=0
2+
use std::cmp::Ordering;
3+
4+
// EMIT_MIR spaceship.foo.built.after.mir
5+
// EMIT_MIR spaceship.bar.built.after.mir
6+
7+
fn foo(a: i32, b: i32) -> Ordering {
8+
// CHECK: [[A:_.+]] = copy _1;
9+
// CHECK: [[B:_.+]] = copy _2;
10+
// CHECK: _0 = Cmp(move [[A]], move [[B]]);
11+
a <=> b
12+
}
13+
14+
fn bar(a: (i32, u32), b: (i32, u32)) -> Ordering {
15+
// CHECK: [[A:_.+]] = &_1;
16+
// CHECK: [[B:_.+]] = &_2;
17+
// CHECK: _0 = <(i32, u32) as Ord>::cmp(move [[A]], move [[B]])
18+
a <=> b
19+
}
20+
21+
fn main() {}

tests/mir-opt/pre-codegen/derived_ord.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
#![crate_type = "lib"]
55

6-
#[derive(PartialOrd, PartialEq)]
6+
#[derive(PartialOrd, PartialEq, Ord, Eq)]
77
pub struct MultiField(char, i16);
88

99
// EMIT_MIR derived_ord.{impl#0}-partial_cmp.PreCodegen.after.mir
10+
// EMIT_MIR derived_ord.{impl#3}-cmp.PreCodegen.after.mir
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// MIR for `<impl at $DIR/derived_ord.rs:6:33: 6:36>::cmp` after PreCodegen
2+
3+
fn <impl at $DIR/derived_ord.rs:6:33: 6:36>::cmp(_1: &MultiField, _2: &MultiField) -> std::cmp::Ordering {
4+
debug self => _1;
5+
debug other => _2;
6+
let mut _0: std::cmp::Ordering;
7+
let mut _3: char;
8+
let mut _4: char;
9+
let mut _5: std::cmp::Ordering;
10+
let mut _6: i8;
11+
let mut _7: i16;
12+
let mut _8: i16;
13+
scope 1 {
14+
debug cmp => _5;
15+
}
16+
17+
bb0: {
18+
StorageLive(_3);
19+
_3 = copy ((*_1).0: char);
20+
StorageLive(_4);
21+
_4 = copy ((*_2).0: char);
22+
_5 = Cmp(move _3, move _4);
23+
StorageDead(_4);
24+
StorageDead(_3);
25+
_6 = discriminant(_5);
26+
switchInt(move _6) -> [0: bb1, otherwise: bb2];
27+
}
28+
29+
bb1: {
30+
StorageLive(_7);
31+
_7 = copy ((*_1).1: i16);
32+
StorageLive(_8);
33+
_8 = copy ((*_2).1: i16);
34+
_0 = Cmp(move _7, move _8);
35+
StorageDead(_8);
36+
StorageDead(_7);
37+
goto -> bb3;
38+
}
39+
40+
bb2: {
41+
_0 = copy _5;
42+
goto -> bb3;
43+
}
44+
45+
bb3: {
46+
return;
47+
}
48+
}

0 commit comments

Comments
 (0)