Chayim Refael Friedman 9d3368f2c2 Properly account for editions in names
This PR touches a lot of parts. But the main changes are changing
`hir_expand::Name` to be raw edition-dependently and only when necessary
(unrelated to how the user originally wrote the identifier),
and changing `is_keyword()` and `is_raw_identifier()` to be edition-aware
(this was done in #17896, but the FIXMEs were fixed here).

It is possible that I missed some cases, but most IDE parts should properly
escape (or not escape) identifiers now.

The rules of thumb are:

 - If we show the identifier to the user, its rawness should be determined
   by the edition of the edited crate. This is nice for IDE features,
   but really important for changes we insert to the source code.
 - For tests, I chose `Edition::CURRENT` (so we only have to (maybe) update
   tests when an edition becomes stable, to avoid churn).
 - For debugging tools (helper methods and logs), I used `Edition::LATEST`.
2024-08-16 16:46:24 +03:00

2885 lines
54 KiB
Rust

use base_db::SourceDatabase;
use chalk_ir::Substitution;
use hir_def::db::DefDatabase;
use rustc_apfloat::{
ieee::{Half as f16, Quad as f128},
Float,
};
use span::EditionedFileId;
use test_fixture::WithFixture;
use test_utils::skip_slow_tests;
use crate::{
consteval::try_const_usize, db::HirDatabase, mir::pad16, test_db::TestDB, Const, ConstScalar,
Interner, MemoryMap,
};
use super::{
super::mir::{MirEvalError, MirLowerError},
ConstEvalError,
};
mod intrinsics;
fn simplify(e: ConstEvalError) -> ConstEvalError {
match e {
ConstEvalError::MirEvalError(MirEvalError::InFunction(e, _)) => {
simplify(ConstEvalError::MirEvalError(*e))
}
_ => e,
}
}
#[track_caller]
fn check_fail(ra_fixture: &str, error: impl FnOnce(ConstEvalError) -> bool) {
let (db, file_id) = TestDB::with_single_file(ra_fixture);
match eval_goal(&db, file_id) {
Ok(_) => panic!("Expected fail, but it succeeded"),
Err(e) => {
assert!(error(simplify(e.clone())), "Actual error was: {}", pretty_print_err(e, db))
}
}
}
#[track_caller]
fn check_number(ra_fixture: &str, answer: i128) {
check_answer(ra_fixture, |b, _| {
assert_eq!(
b,
&answer.to_le_bytes()[0..b.len()],
"Bytes differ. In decimal form: actual = {}, expected = {answer}",
i128::from_le_bytes(pad16(b, true))
);
});
}
#[track_caller]
fn check_str(ra_fixture: &str, answer: &str) {
check_answer(ra_fixture, |b, mm| {
let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap());
let size = usize::from_le_bytes(b[b.len() / 2..].try_into().unwrap());
let Some(bytes) = mm.get(addr, size) else {
panic!("string data missed in the memory map");
};
assert_eq!(
bytes,
answer.as_bytes(),
"Bytes differ. In string form: actual = {}, expected = {answer}",
String::from_utf8_lossy(bytes)
);
});
}
#[track_caller]
fn check_answer(ra_fixture: &str, check: impl FnOnce(&[u8], &MemoryMap)) {
let (db, file_ids) = TestDB::with_many_files(ra_fixture);
let file_id = *file_ids.last().unwrap();
let r = match eval_goal(&db, file_id) {
Ok(t) => t,
Err(e) => {
let err = pretty_print_err(e, db);
panic!("Error in evaluating goal: {err}");
}
};
match &r.data(Interner).value {
chalk_ir::ConstValue::Concrete(c) => match &c.interned {
ConstScalar::Bytes(b, mm) => {
check(b, mm);
}
x => panic!("Expected number but found {x:?}"),
},
_ => panic!("result of const eval wasn't a concrete const"),
}
}
fn pretty_print_err(e: ConstEvalError, db: TestDB) -> String {
let mut err = String::new();
let span_formatter = |file, range| format!("{file:?} {range:?}");
let edition = db.crate_graph()[db.test_crate()].edition;
match e {
ConstEvalError::MirLowerError(e) => e.pretty_print(&mut err, &db, span_formatter, edition),
ConstEvalError::MirEvalError(e) => e.pretty_print(&mut err, &db, span_formatter, edition),
}
.unwrap();
err
}
fn eval_goal(db: &TestDB, file_id: EditionedFileId) -> Result<Const, ConstEvalError> {
let module_id = db.module_for_file(file_id.file_id());
let def_map = module_id.def_map(db);
let scope = &def_map[module_id.local_id].scope;
let const_id = scope
.declarations()
.find_map(|x| match x {
hir_def::ModuleDefId::ConstId(x) => {
if db.const_data(x).name.as_ref()?.display(db, file_id.edition()).to_string()
== "GOAL"
{
Some(x)
} else {
None
}
}
_ => None,
})
.expect("No const named GOAL found in the test");
db.const_eval(const_id.into(), Substitution::empty(Interner), None)
}
#[test]
fn add() {
check_number(r#"const GOAL: usize = 2 + 2;"#, 4);
check_number(r#"const GOAL: i32 = -2 + --5;"#, 3);
check_number(r#"const GOAL: i32 = 7 - 5;"#, 2);
check_number(r#"const GOAL: i32 = 7 + (1 - 5);"#, 3);
}
#[test]
fn bit_op() {
check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128);
check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0);
check_number(r#"const GOAL: i8 = 1 << 7"#, (1i8 << 7) as i128);
check_number(r#"const GOAL: i8 = -1 << 2"#, (-1i8 << 2) as i128);
check_fail(r#"const GOAL: i8 = 1 << 8"#, |e| {
e == ConstEvalError::MirEvalError(MirEvalError::Panic("Overflow in Shl".to_owned()))
});
check_number(r#"const GOAL: i32 = 100000000i32 << 11"#, (100000000i32 << 11) as i128);
}
#[test]
fn floating_point() {
check_number(
r#"const GOAL: f128 = 2.0 + 3.0 * 5.5 - 8.;"#,
"10.5".parse::<f128>().unwrap().to_bits() as i128,
);
check_number(
r#"const GOAL: f128 = -90.0 + 36.0;"#,
"-54.0".parse::<f128>().unwrap().to_bits() as i128,
);
check_number(
r#"const GOAL: f64 = 2.0 + 3.0 * 5.5 - 8.;"#,
i128::from_le_bytes(pad16(&f64::to_le_bytes(10.5), true)),
);
check_number(
r#"const GOAL: f32 = 2.0 + 3.0 * 5.5 - 8.;"#,
i128::from_le_bytes(pad16(&f32::to_le_bytes(10.5), true)),
);
check_number(
r#"const GOAL: f32 = -90.0 + 36.0;"#,
i128::from_le_bytes(pad16(&f32::to_le_bytes(-54.0), true)),
);
check_number(
r#"const GOAL: f16 = 2.0 + 3.0 * 5.5 - 8.;"#,
i128::from_le_bytes(pad16(
&u16::try_from("10.5".parse::<f16>().unwrap().to_bits()).unwrap().to_le_bytes(),
true,
)),
);
check_number(
r#"const GOAL: f16 = -90.0 + 36.0;"#,
i128::from_le_bytes(pad16(
&u16::try_from("-54.0".parse::<f16>().unwrap().to_bits()).unwrap().to_le_bytes(),
true,
)),
);
}
#[test]
fn casts() {
check_number(r#"const GOAL: usize = 12 as *const i32 as usize"#, 12);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: i32 = {
let a = [10, 20, 3, 15];
let x: &[i32] = &a;
let y: *const [i32] = x;
let z = y as *const i32;
unsafe { *z }
};
"#,
10,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: i16 = {
let a = &mut 5;
let z = a as *mut _;
unsafe { *z }
};
"#,
5,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = {
let a = &[10, 20, 30, 40] as &[i32];
a.len()
};
"#,
4,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
struct X {
unsize_field: [u8],
}
const GOAL: usize = {
let a = [10, 20, 3, 15];
let x: &[i32] = &a;
let x: *const [i32] = x;
let x = x as *const [u8]; // slice fat pointer cast don't touch metadata
let x = x as *const str;
let x = x as *const X;
let x = x as *const [i16];
let x = x as *const X;
let x = x as *const [u8];
let w = unsafe { &*x };
w.len()
};
"#,
4,
);
check_number(r#"const GOAL: i32 = -12i8 as i32"#, -12);
}
#[test]
fn raw_pointer_equality() {
check_number(
r#"
//- minicore: copy, eq
const GOAL: bool = {
let a = 2;
let p1 = a as *const i32;
let p2 = a as *const i32;
p1 == p2
};
"#,
1,
);
}
#[test]
fn alignment() {
check_answer(
r#"
//- minicore: transmute
use core::mem::transmute;
const GOAL: usize = {
let x: i64 = 2;
transmute(&x)
}
"#,
|b, _| assert_eq!(b[0] % 8, 0),
);
check_answer(
r#"
//- minicore: transmute
use core::mem::transmute;
static X: i64 = 12;
const GOAL: usize = transmute(&X);
"#,
|b, _| assert_eq!(b[0] % 8, 0),
);
}
#[test]
fn locals() {
check_number(
r#"
const GOAL: usize = {
let a = 3 + 2;
let b = a * a;
b
};
"#,
25,
);
}
#[test]
fn references() {
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &mut x;
*y = 5;
x
};
"#,
5,
);
check_number(
r#"
struct Foo(i32);
impl Foo {
fn method(&mut self, x: i32) {
self.0 = 2 * self.0 + x;
}
}
const GOAL: i32 = {
let mut x = Foo(3);
x.method(5);
x.0
};
"#,
11,
);
}
#[test]
fn reference_autoderef() {
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &mut x;
let y: &mut usize = &mut y;
*y = 5;
x
};
"#,
5,
);
check_number(
r#"
const GOAL: usize = {
let x = 3;
let y = &&&&&&&x;
let z: &usize = &y;
*z
};
"#,
3,
);
check_number(
r#"
struct Foo<T> { x: T }
impl<T> Foo<T> {
fn foo(&mut self) -> T { self.x }
}
fn f(i: &mut &mut Foo<Foo<i32>>) -> i32 {
((**i).x).foo()
}
fn g(i: Foo<Foo<i32>>) -> i32 {
i.x.foo()
}
const GOAL: i32 = f(&mut &mut Foo { x: Foo { x: 3 } }) + g(Foo { x: Foo { x: 5 } });
"#,
8,
);
}
#[test]
fn overloaded_deref() {
check_number(
r#"
//- minicore: deref_mut
struct Foo;
impl core::ops::Deref for Foo {
type Target = i32;
fn deref(&self) -> &i32 {
&5
}
}
const GOAL: i32 = {
let x = Foo;
let y = &*x;
*y + *x
};
"#,
10,
);
}
#[test]
fn overloaded_deref_autoref() {
check_number(
r#"
//- minicore: deref_mut
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
type Target = Bar;
fn deref(&self) -> &Bar {
&Bar
}
}
impl Bar {
fn method(&self) -> i32 {
5
}
}
const GOAL: i32 = Foo.method();
"#,
5,
);
}
#[test]
fn overloaded_index() {
check_number(
r#"
//- minicore: index
struct Foo;
impl core::ops::Index<usize> for Foo {
type Output = i32;
fn index(&self, index: usize) -> &i32 {
if index == 7 {
&700
} else {
&1000
}
}
}
impl core::ops::IndexMut<usize> for Foo {
fn index_mut(&mut self, index: usize) -> &mut i32 {
if index == 7 {
&mut 7
} else {
&mut 10
}
}
}
const GOAL: i32 = {
(Foo[2]) + (Foo[7]) + (*&Foo[2]) + (*&Foo[7]) + (*&mut Foo[2]) + (*&mut Foo[7])
};
"#,
3417,
);
}
#[test]
fn overloaded_binop() {
check_number(
r#"
//- minicore: add
enum Color {
Red,
Green,
Yellow,
}
use Color::*;
impl core::ops::Add for Color {
type Output = Color;
fn add(self, rhs: Color) -> Self::Output {
Yellow
}
}
impl core::ops::AddAssign for Color {
fn add_assign(&mut self, rhs: Color) {
*self = Red;
}
}
const GOAL: bool = {
let x = Red + Green;
let mut y = Green;
y += x;
x == Yellow && y == Red && Red + Green == Yellow && Red + Red == Yellow && Yellow + Green == Yellow
};
"#,
1,
);
check_number(
r#"
//- minicore: add
impl core::ops::Add for usize {
type Output = usize;
fn add(self, rhs: usize) -> Self::Output {
self + rhs
}
}
impl core::ops::AddAssign for usize {
fn add_assign(&mut self, rhs: usize) {
*self += rhs;
}
}
#[lang = "shl"]
pub trait Shl<Rhs = Self> {
type Output;
fn shl(self, rhs: Rhs) -> Self::Output;
}
impl Shl<u8> for usize {
type Output = usize;
fn shl(self, rhs: u8) -> Self::Output {
self << rhs
}
}
const GOAL: usize = {
let mut x = 10;
x += 20;
2 + 2 + (x << 1u8)
};"#,
64,
);
}
#[test]
fn function_call() {
check_number(
r#"
const fn f(x: usize) -> usize {
2 * x + 5
}
const GOAL: usize = f(3);
"#,
11,
);
check_number(
r#"
const fn add(x: usize, y: usize) -> usize {
x + y
}
const GOAL: usize = add(add(1, 2), add(3, add(4, 5)));
"#,
15,
);
}
#[test]
fn trait_basic() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
const GOAL: u8 = {
let x = 3;
Foo::f(&x)
};
"#,
36,
);
}
#[test]
fn trait_method() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
const GOAL: u8 = {
let x = 3;
x.f()
};
"#,
36,
);
}
#[test]
fn trait_method_inside_block() {
check_number(
r#"
trait Twait {
fn a(&self) -> i32;
}
fn outer() -> impl Twait {
struct Stwuct;
impl Twait for Stwuct {
fn a(&self) -> i32 {
5
}
}
fn f() -> impl Twait {
let s = Stwuct;
s
}
f()
}
const GOAL: i32 = outer().a();
"#,
5,
);
}
#[test]
fn generic_fn() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for () {
fn f(&self) -> u8 {
0
}
}
struct Succ<S>(S);
impl<T: Foo> Foo for Succ<T> {
fn f(&self) -> u8 {
self.0.f() + 1
}
}
const GOAL: u8 = Succ(Succ(())).f();
"#,
2,
);
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
fn foof<T: Foo>(x: T, y: T) -> u8 {
x.f() + y.f()
}
const GOAL: u8 = foof(2, 5);
"#,
73,
);
check_number(
r#"
fn bar<A, B>(a: A, b: B) -> B {
b
}
const GOAL: u8 = bar("hello", 12);
"#,
12,
);
check_number(
r#"
const fn y<T>(b: T) -> (T, ) {
let alloc = b;
(alloc, )
}
const GOAL: u8 = y(2).0;
"#,
2,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
fn bar<A, B>(a: A, b: B) -> B {
b
}
fn foo<T>(x: [T; 2]) -> T {
bar(x[0], x[1])
}
const GOAL: u8 = foo([2, 5]);
"#,
5,
);
}
#[test]
fn impl_trait() {
check_number(
r#"
trait Foo {
fn f(&self) -> u8;
}
impl Foo for u8 {
fn f(&self) -> u8 {
*self + 33
}
}
fn foof(x: impl Foo, y: impl Foo) -> impl Foo {
x.f() + y.f()
}
const GOAL: u8 = foof(2, 5).f();
"#,
106,
);
check_number(
r#"
struct Foo<T>(T, T, (T, T));
trait S {
fn sum(&self) -> i64;
}
impl S for i64 {
fn sum(&self) -> i64 {
*self
}
}
impl<T: S> S for Foo<T> {
fn sum(&self) -> i64 {
self.0.sum() + self.1.sum() + self.2 .0.sum() + self.2 .1.sum()
}
}
fn foo() -> Foo<impl S> {
Foo(
Foo(1i64, 2, (3, 4)),
Foo(5, 6, (7, 8)),
(
Foo(9, 10, (11, 12)),
Foo(13, 14, (15, 16)),
),
)
}
const GOAL: i64 = foo().sum();
"#,
136,
);
}
#[test]
fn ifs() {
check_number(
r#"
const fn f(b: bool) -> u8 {
if b { 1 } else { 10 }
}
const GOAL: u8 = f(true) + f(true) + f(false);
"#,
12,
);
check_number(
r#"
const fn max(a: i32, b: i32) -> i32 {
if a < b { b } else { a }
}
const GOAL: i32 = max(max(1, max(10, 3)), 0-122);
"#,
10,
);
check_number(
r#"
const fn max(a: &i32, b: &i32) -> &i32 {
if *a < *b { b } else { a }
}
const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
"#,
10,
);
}
#[test]
fn loops() {
check_number(
r#"
const GOAL: u8 = {
let mut x = 0;
loop {
x = x + 1;
while true {
break;
}
x = x + 1;
if x == 2 {
continue;
}
break;
}
x
};
"#,
4,
);
check_number(
r#"
const GOAL: u8 = {
let mut x = 0;
loop {
x = x + 1;
if x == 5 {
break x + 2;
}
}
};
"#,
7,
);
check_number(
r#"
const GOAL: u8 = {
'a: loop {
let x = 'b: loop {
let x = 'c: loop {
let x = 'd: loop {
let x = 'e: loop {
break 'd 1;
};
break 2 + x;
};
break 3 + x;
};
break 'a 4 + x;
};
break 5 + x;
}
};
"#,
8,
);
check_number(
r#"
//- minicore: add
const GOAL: u8 = {
let mut x = 0;
'a: loop {
'b: loop {
'c: while x < 20 {
'd: while x < 5 {
'e: loop {
x += 1;
continue 'c;
};
};
x += 1;
};
break 'a;
};
}
x
};
"#,
20,
);
}
#[test]
fn for_loops() {
check_number(
r#"
//- minicore: iterator
struct Range {
start: u8,
end: u8,
}
impl Iterator for Range {
type Item = u8;
fn next(&mut self) -> Option<u8> {
if self.start >= self.end {
None
} else {
let r = self.start;
self.start = self.start + 1;
Some(r)
}
}
}
const GOAL: u8 = {
let mut sum = 0;
let ar = Range { start: 1, end: 11 };
for i in ar {
sum = sum + i;
}
sum
};
"#,
55,
);
}
#[test]
fn ranges() {
check_number(
r#"
//- minicore: range
const GOAL: i32 = (1..2).start + (20..10).end + (100..=200).start + (2000..=1000).end
+ (10000..).start + (..100000).end + (..=1000000).end;
"#,
1111111,
);
}
#[test]
fn recursion() {
check_number(
r#"
const fn fact(k: i32) -> i32 {
if k > 0 { fact(k - 1) * k } else { 1 }
}
const GOAL: i32 = fact(5);
"#,
120,
);
}
#[test]
fn structs() {
check_number(
r#"
struct Point {
x: i32,
y: i32,
}
const GOAL: i32 = {
let p = Point { x: 5, y: 2 };
let y = 1;
let x = 3;
let q = Point { y, x };
p.x + p.y + p.x + q.y + q.y + q.x
};
"#,
17,
);
check_number(
r#"
struct Point {
x: i32,
y: i32,
}
const GOAL: i32 = {
let p = Point { x: 5, y: 2 };
let p2 = Point { x: 3, ..p };
p.x * 1000 + p.y * 100 + p2.x * 10 + p2.y
};
"#,
5232,
);
check_number(
r#"
struct Point {
x: i32,
y: i32,
}
const GOAL: i32 = {
let p = Point { x: 5, y: 2 };
let Point { x, y } = p;
let Point { x: x2, .. } = p;
let Point { y: y2, .. } = p;
x * 1000 + y * 100 + x2 * 10 + y2
};
"#,
5252,
);
}
#[test]
fn unions() {
check_number(
r#"
union U {
f1: i64,
f2: (i32, i32),
}
const GOAL: i32 = {
let p = U { f1: 0x0123ABCD0123DCBA };
let p = unsafe { p.f2 };
p.0 + p.1 + p.1
};
"#,
0x0123ABCD * 2 + 0x0123DCBA,
);
}
#[test]
fn tuples() {
check_number(
r#"
const GOAL: u8 = {
let a = (10, 20, 3, 15);
a.1
};
"#,
20,
);
check_number(
r#"
const GOAL: u8 = {
let mut a = (10, 20, 3, 15);
a.1 = 2;
a.0 + a.1 + a.2 + a.3
};
"#,
30,
);
check_number(
r#"
struct TupleLike(i32, i64, u8, u16);
const GOAL: i64 = {
let a = TupleLike(10, 20, 3, 15);
let TupleLike(b, .., c) = a;
a.1 * 100 + b as i64 + c as i64
};
"#,
2025,
);
check_number(
r#"
const GOAL: u8 = {
match (&(2 + 2), &4) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
2
} else {
5
}
}
}
};
"#,
5,
);
}
#[test]
fn path_pattern_matching() {
check_number(
r#"
enum Season {
Spring,
Summer,
Fall,
Winter,
}
use Season::*;
const MY_SEASON: Season = Summer;
impl Season {
const FALL: Season = Fall;
}
const fn f(x: Season) -> i32 {
match x {
Spring => 1,
MY_SEASON => 2,
Season::FALL => 3,
Winter => 4,
}
}
const GOAL: i32 = f(Spring) + 10 * f(Summer) + 100 * f(Fall) + 1000 * f(Winter);
"#,
4321,
);
}
#[test]
fn pattern_matching_literal() {
check_number(
r#"
const fn f(x: i32) -> i32 {
match x {
-1 => 1,
1 => 10,
_ => 100,
}
}
const GOAL: i32 = f(-1) + f(1) + f(0) + f(-5);
"#,
211,
);
check_number(
r#"
const fn f(x: &str) -> i32 {
match x {
"f" => 1,
"foo" => 10,
"" => 100,
"bar" => 1000,
_ => 10000,
}
}
const GOAL: i32 = f("f") + f("foo") * 2 + f("") * 3 + f("bar") * 4;
"#,
4321,
);
}
#[test]
fn pattern_matching_range() {
check_number(
r#"
pub const L: i32 = 6;
mod x {
pub const R: i32 = 100;
}
const fn f(x: i32) -> i32 {
match x {
-1..=5 => x * 10,
L..=x::R => x * 100,
_ => x,
}
}
const GOAL: i32 = f(-1) + f(2) + f(100) + f(-2) + f(1000);
"#,
11008,
);
}
#[test]
fn pattern_matching_slice() {
check_number(
r#"
//- minicore: slice, index, coerce_unsized, copy
const fn f(x: &[usize]) -> usize {
match x {
[a, b @ .., c, d] => *a + b.len() + *c + *d,
}
}
const GOAL: usize = f(&[10, 20, 3, 15, 1000, 60, 16]);
"#,
10 + 4 + 60 + 16,
);
check_number(
r#"
//- minicore: slice, index, coerce_unsized, copy
const fn f(x: &[usize]) -> usize {
match x {
[] => 0,
[a] => *a,
&[a, b] => a + b,
[a, b @ .., c, d] => *a + b.len() + *c + *d,
}
}
const GOAL: usize = f(&[]) + f(&[10]) + f(&[100, 100])
+ f(&[1000, 1000, 1000]) + f(&[10000, 57, 34, 46, 10000, 10000]);
"#,
33213,
);
check_number(
r#"
//- minicore: slice, index, coerce_unsized, copy
const fn f(mut slice: &[u32]) -> usize {
slice = match slice {
[0, rest @ ..] | rest => rest,
};
slice.len()
}
const GOAL: usize = f(&[]) + f(&[10]) + f(&[0, 100])
+ f(&[1000, 1000, 1000]) + f(&[0, 57, 34, 46, 10000, 10000]);
"#,
10,
);
}
#[test]
fn pattern_matching_ergonomics() {
check_number(
r#"
const fn f(x: &(u8, u8)) -> u8 {
match x {
(a, b) => *a + *b
}
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
check_number(
r#"
const GOAL: u8 = {
let a = &(2, 3);
let &(x, y) = a;
x + y
};
"#,
5,
);
}
#[test]
fn destructing_assignment() {
check_number(
r#"
//- minicore: add
const fn f(i: &mut u8) -> &mut u8 {
*i += 1;
i
}
const GOAL: u8 = {
let mut i = 4;
_ = f(&mut i);
i
};
"#,
5,
);
check_number(
r#"
const GOAL: u8 = {
let (mut a, mut b) = (2, 5);
(a, b) = (b, a);
a * 10 + b
};
"#,
52,
);
check_number(
r#"
struct Point { x: i32, y: i32 }
const GOAL: i32 = {
let mut p = Point { x: 5, y: 6 };
(p.x, _) = (p.y, p.x);
p.x * 10 + p.y
};
"#,
66,
);
}
#[test]
fn let_else() {
check_number(
r#"
const fn f(x: &(u8, u8)) -> u8 {
let (a, b) = x;
*a + *b
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
check_number(
r#"
enum SingleVariant {
Var(u8, u8),
}
const fn f(x: &&&&&SingleVariant) -> u8 {
let SingleVariant::Var(a, b) = x;
*a + *b
}
const GOAL: u8 = f(&&&&&SingleVariant::Var(2, 3));
"#,
5,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>) -> i32 {
let Some(x) = x else { return 10 };
2 * x
}
const GOAL: i32 = f(Some(1000)) + f(None);
"#,
2010,
);
}
#[test]
fn function_param_patterns() {
check_number(
r#"
const fn f((a, b): &(u8, u8)) -> u8 {
*a + *b
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
check_number(
r#"
const fn f(c @ (a, b): &(u8, u8)) -> u8 {
*a + *b + c.0 + (*c).1
}
const GOAL: u8 = f(&(2, 3));
"#,
10,
);
check_number(
r#"
const fn f(ref a: u8) -> u8 {
*a
}
const GOAL: u8 = f(2);
"#,
2,
);
check_number(
r#"
struct Foo(u8);
impl Foo {
const fn f(&self, (a, b): &(u8, u8)) -> u8 {
self.0 + *a + *b
}
}
const GOAL: u8 = Foo(4).f(&(2, 3));
"#,
9,
);
}
#[test]
fn match_guards() {
check_number(
r#"
//- minicore: option
fn f(x: Option<i32>) -> i32 {
match x {
y if let Some(42) = y => 42000,
Some(y) => y,
None => 10
}
}
const GOAL: i32 = f(Some(42)) + f(Some(2)) + f(None);
"#,
42012,
);
}
#[test]
fn result_layout_niche_optimization() {
check_number(
r#"
//- minicore: option, result
const GOAL: i32 = match Some(2).ok_or(Some(2)) {
Ok(x) => x,
Err(_) => 1000,
};
"#,
2,
);
check_number(
r#"
//- minicore: result
pub enum AlignmentEnum64 {
_Align1Shl0 = 1 << 0,
_Align1Shl1 = 1 << 1,
_Align1Shl2 = 1 << 2,
_Align1Shl3 = 1 << 3,
_Align1Shl4 = 1 << 4,
_Align1Shl5 = 1 << 5,
}
const GOAL: Result<AlignmentEnum64, ()> = {
let align = Err(());
align
};
"#,
0, // It is 0 since result is niche encoded and 1 is valid for `AlignmentEnum64`
);
check_number(
r#"
//- minicore: result
pub enum AlignmentEnum64 {
_Align1Shl0 = 1 << 0,
_Align1Shl1 = 1 << 1,
_Align1Shl2 = 1 << 2,
_Align1Shl3 = 1 << 3,
_Align1Shl4 = 1 << 4,
_Align1Shl5 = 1 << 5,
}
const GOAL: i32 = {
let align = Ok::<_, ()>(AlignmentEnum64::_Align1Shl0);
match align {
Ok(_) => 2,
Err(_) => 1,
}
};
"#,
2,
);
}
#[test]
fn options() {
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let x = Some(2);
match x {
Some(y) => 2 * y,
_ => 10,
}
};
"#,
4,
);
check_number(
r#"
//- minicore: option
fn f(x: Option<Option<i32>>) -> i32 {
if let Some(y) = x && let Some(z) = y {
z
} else if let Some(y) = x {
1
} else {
0
}
}
const GOAL: i32 = f(Some(Some(10))) + f(Some(None)) + f(None);
"#,
11,
);
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let x = None;
match x {
Some(y) => 2 * y,
_ => 10,
}
};
"#,
10,
);
check_number(
r#"
//- minicore: option
const GOAL: Option<&u8> = None;
"#,
0,
);
}
#[test]
fn from_trait() {
check_number(
r#"
//- minicore: from
struct E1(i32);
struct E2(i32);
impl From<E1> for E2 {
fn from(E1(x): E1) -> Self {
E2(1000 * x)
}
}
const GOAL: i32 = {
let x: E2 = E1(2).into();
x.0
};
"#,
2000,
);
}
#[test]
fn closure_clone() {
check_number(
r#"
//- minicore: clone, fn
struct S(u8);
impl Clone for S(u8) {
fn clone(&self) -> S {
S(self.0 + 5)
}
}
const GOAL: u8 = {
let s = S(3);
let cl = move || s;
let cl = cl.clone();
cl().0
}
"#,
8,
);
}
#[test]
fn builtin_derive_macro() {
check_number(
r#"
//- minicore: clone, derive, builtin_impls
#[derive(Clone)]
enum Z {
Foo(Y),
Bar,
}
#[derive(Clone)]
struct X(i32, Z, i64)
#[derive(Clone)]
struct Y {
field1: i32,
field2: ((i32, u8), i64),
}
const GOAL: u8 = {
let x = X(2, Z::Foo(Y { field1: 4, field2: ((32, 5), 12) }), 8);
let x = x.clone();
let Z::Foo(t) = x.1;
t.field2.0 .1
};
"#,
5,
);
check_number(
r#"
//- minicore: default, derive, builtin_impls
#[derive(Default)]
struct X(i32, Y, i64)
#[derive(Default)]
struct Y {
field1: i32,
field2: u8,
}
const GOAL: u8 = {
let x = X::default();
x.1.field2
};
"#,
0,
);
}
#[test]
fn try_operator() {
check_number(
r#"
//- minicore: option, try
const fn f(x: Option<i32>, y: Option<i32>) -> Option<i32> {
Some(x? * y?)
}
const fn g(x: Option<i32>, y: Option<i32>) -> i32 {
match f(x, y) {
Some(k) => k,
None => 5,
}
}
const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None);
"#,
215,
);
check_number(
r#"
//- minicore: result, try, from
struct E1(i32);
struct E2(i32);
impl From<E1> for E2 {
fn from(E1(x): E1) -> Self {
E2(1000 * x)
}
}
const fn f(x: Result<i32, E1>) -> Result<i32, E2> {
Ok(x? * 10)
}
const fn g(x: Result<i32, E1>) -> i32 {
match f(x) {
Ok(k) => 7 * k,
Err(E2(k)) => 5 * k,
}
}
const GOAL: i32 = g(Ok(2)) + g(Err(E1(3)));
"#,
15140,
);
}
#[test]
fn try_block() {
check_number(
r#"
//- minicore: option, try
const fn g(x: Option<i32>, y: Option<i32>) -> i32 {
let r = try { x? * y? };
match r {
Some(k) => k,
None => 5,
}
}
const GOAL: i32 = g(Some(10), Some(20)) + g(Some(30), None) + g(None, Some(40)) + g(None, None);
"#,
215,
);
}
#[test]
fn closures() {
check_number(
r#"
//- minicore: fn, copy
const GOAL: i32 = {
let y = 5;
let c = |x| x + y;
c(2)
};
"#,
7,
);
check_number(
r#"
//- minicore: fn, copy
const GOAL: i32 = {
let y = 5;
let c = |(a, b): &(i32, i32)| *a + *b + y;
c(&(2, 3))
};
"#,
10,
);
check_number(
r#"
//- minicore: fn, copy
const GOAL: i32 = {
let mut y = 5;
let c = |x| {
y = y + x;
};
c(2);
c(3);
y
};
"#,
10,
);
check_number(
r#"
//- minicore: fn, copy
const GOAL: i32 = {
let c: fn(i32) -> i32 = |x| 2 * x;
c(2) + c(10)
};
"#,
24,
);
check_number(
r#"
//- minicore: fn, copy
struct X(i32);
impl X {
fn mult(&mut self, n: i32) {
self.0 = self.0 * n
}
}
const GOAL: i32 = {
let x = X(1);
let c = || {
x.mult(2);
|| {
x.mult(3);
|| {
|| {
x.mult(4);
|| {
x.mult(x.0);
|| {
x.0
}
}
}
}
}
};
let r = c()()()()()();
r + x.0
};
"#,
24 * 24 * 2,
);
}
#[test]
fn manual_fn_trait_impl() {
check_number(
r#"
//- minicore: fn, copy
struct S(i32);
impl FnOnce<(i32, i32)> for S {
type Output = i32;
extern "rust-call" fn call_once(self, arg: (i32, i32)) -> i32 {
arg.0 + arg.1 + self.0
}
}
const GOAL: i32 = {
let s = S(1);
s(2, 3)
};
"#,
6,
);
}
#[test]
fn closure_capture_unsized_type() {
check_number(
r#"
//- minicore: fn, copy, slice, index, coerce_unsized
fn f<T: A>(x: &<T as A>::Ty) -> &<T as A>::Ty {
let c = || &*x;
c()
}
trait A {
type Ty;
}
impl A for i32 {
type Ty = [u8];
}
const GOAL: u8 = {
let k: &[u8] = &[1, 2, 3];
let k = f::<i32>(k);
k[0] + k[1] + k[2]
}
"#,
6,
);
}
#[test]
fn closure_and_impl_fn() {
check_number(
r#"
//- minicore: fn, copy
fn closure_wrapper<F: FnOnce() -> i32>(c: F) -> impl FnOnce() -> F {
|| c
}
const GOAL: i32 = {
let y = 5;
let c = closure_wrapper(|| y);
c()()
};
"#,
5,
);
check_number(
r#"
//- minicore: fn, copy
fn f<T, F: Fn() -> T>(t: F) -> impl Fn() -> T {
move || t()
}
const GOAL: i32 = f(|| 2)();
"#,
2,
);
}
#[test]
fn or_pattern() {
check_number(
r#"
const GOAL: u8 = {
let (a | a) = 2;
a
};
"#,
2,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>) -> i32 {
let (Some(a) | Some(a)) = x else { return 2; };
a
}
const GOAL: i32 = f(Some(10)) + f(None);
"#,
12,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>, y: Option<i32>) -> i32 {
match (x, y) {
(Some(x), Some(y)) => x * y,
(Some(a), _) | (_, Some(a)) => a,
_ => 10,
}
}
const GOAL: i32 = f(Some(10), Some(20)) + f(Some(30), None) + f(None, Some(40)) + f(None, None);
"#,
280,
);
}
#[test]
fn function_pointer_in_constants() {
check_number(
r#"
struct Foo {
f: fn(u8) -> u8,
}
const FOO: Foo = Foo { f: add2 };
fn add2(x: u8) -> u8 {
x + 2
}
const GOAL: u8 = (FOO.f)(3);
"#,
5,
);
}
#[test]
fn function_pointer_and_niche_optimization() {
check_number(
r#"
//- minicore: option
const GOAL: i32 = {
let f: fn(i32) -> i32 = |x| x + 2;
let init = Some(f);
match init {
Some(t) => t(3),
None => 222,
}
};
"#,
5,
);
}
#[test]
fn function_pointer() {
check_number(
r#"
fn add2(x: u8) -> u8 {
x + 2
}
const GOAL: u8 = {
let plus2 = add2;
plus2(3)
};
"#,
5,
);
check_number(
r#"
fn add2(x: u8) -> u8 {
x + 2
}
const GOAL: u8 = {
let plus2: fn(u8) -> u8 = add2;
plus2(3)
};
"#,
5,
);
check_number(
r#"
fn add2(x: u8) -> u8 {
x + 2
}
const GOAL: u8 = {
let plus2 = add2 as fn(u8) -> u8;
plus2(3)
};
"#,
5,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
fn add2(x: u8) -> u8 {
x + 2
}
fn mult3(x: u8) -> u8 {
x * 3
}
const GOAL: u8 = {
let x = [add2, mult3];
x[0](1) + x[1](5)
};
"#,
18,
);
}
#[test]
fn enum_variant_as_function() {
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let f = Some;
f(3).unwrap_or(2)
};
"#,
3,
);
check_number(
r#"
//- minicore: option
const GOAL: u8 = {
let f: fn(u8) -> Option<u8> = Some;
f(3).unwrap_or(2)
};
"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
enum Foo {
Add2(u8),
Mult3(u8),
}
use Foo::*;
const fn f(x: Foo) -> u8 {
match x {
Add2(x) => x + 2,
Mult3(x) => x * 3,
}
}
const GOAL: u8 = {
let x = [Add2, Mult3];
f(x[0](1)) + f(x[1](5))
};
"#,
18,
);
}
#[test]
fn function_traits() {
check_number(
r#"
//- minicore: fn
fn add2(x: u8) -> u8 {
x + 2
}
fn call(f: impl Fn(u8) -> u8, x: u8) -> u8 {
f(x)
}
fn call_mut(mut f: impl FnMut(u8) -> u8, x: u8) -> u8 {
f(x)
}
fn call_once(f: impl FnOnce(u8) -> u8, x: u8) -> u8 {
f(x)
}
const GOAL: u8 = call(add2, 3) + call_mut(add2, 3) + call_once(add2, 3);
"#,
15,
);
check_number(
r#"
//- minicore: coerce_unsized, fn
fn add2(x: u8) -> u8 {
x + 2
}
fn call(f: &dyn Fn(u8) -> u8, x: u8) -> u8 {
f(x)
}
fn call_mut(f: &mut dyn FnMut(u8) -> u8, x: u8) -> u8 {
f(x)
}
const GOAL: u8 = call(&add2, 3) + call_mut(&mut add2, 3);
"#,
10,
);
check_number(
r#"
//- minicore: fn
fn add2(x: u8) -> u8 {
x + 2
}
fn call(f: impl Fn(u8) -> u8, x: u8) -> u8 {
f(x)
}
fn call_mut(mut f: impl FnMut(u8) -> u8, x: u8) -> u8 {
f(x)
}
fn call_once(f: impl FnOnce(u8) -> u8, x: u8) -> u8 {
f(x)
}
const GOAL: u8 = {
let add2: fn(u8) -> u8 = add2;
call(add2, 3) + call_mut(add2, 3) + call_once(add2, 3)
};
"#,
15,
);
check_number(
r#"
//- minicore: fn
fn add2(x: u8) -> u8 {
x + 2
}
fn call(f: &&&&&impl Fn(u8) -> u8, x: u8) -> u8 {
f(x)
}
const GOAL: u8 = call(&&&&&add2, 3);
"#,
5,
);
}
#[test]
fn dyn_trait() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
trait Foo {
fn foo(&self) -> u8 { 10 }
}
struct S1;
struct S2;
struct S3;
impl Foo for S1 {
fn foo(&self) -> u8 { 1 }
}
impl Foo for S2 {
fn foo(&self) -> u8 { 2 }
}
impl Foo for S3 {}
const GOAL: u8 = {
let x: &[&dyn Foo] = &[&S1, &S2, &S3];
x[0].foo() + x[1].foo() + x[2].foo()
};
"#,
13,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
trait Foo {
fn foo(&self) -> i32 { 10 }
}
trait Bar {
fn bar(&self) -> i32 { 20 }
}
struct S;
impl Foo for S {
fn foo(&self) -> i32 { 200 }
}
impl Bar for dyn Foo {
fn bar(&self) -> i32 { 700 }
}
const GOAL: i32 = {
let x: &dyn Foo = &S;
x.bar() + x.foo()
};
"#,
900,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
trait A {
fn x(&self) -> i32;
}
trait B: A {}
impl A for i32 {
fn x(&self) -> i32 {
5
}
}
impl B for i32 {
}
const fn f(x: &dyn B) -> i32 {
x.x()
}
const GOAL: i32 = f(&2i32);
"#,
5,
);
}
#[test]
fn coerce_unsized() {
check_number(
r#"
//- minicore: coerce_unsized, deref_mut, slice, index, transmute, non_null
use core::ops::{Deref, DerefMut, CoerceUnsized};
use core::{marker::Unsize, mem::transmute, ptr::NonNull};
struct ArcInner<T: ?Sized> {
strong: usize,
weak: usize,
data: T,
}
pub struct Arc<T: ?Sized> {
inner: NonNull<ArcInner<T>>,
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Arc<U>> for Arc<T> {}
const GOAL: usize = {
let x = transmute::<usize, Arc<[i32; 3]>>(12);
let y: Arc<[i32]> = x;
let z = transmute::<Arc<[i32]>, (usize, usize)>(y);
z.1
};
"#,
3,
);
}
#[test]
fn boxes() {
check_number(
r#"
//- minicore: coerce_unsized, deref_mut, slice
use core::ops::{Deref, DerefMut};
use core::{marker::Unsize, ops::CoerceUnsized};
#[lang = "owned_box"]
pub struct Box<T: ?Sized> {
inner: *mut T,
}
impl<T> Box<T> {
fn new(t: T) -> Self {
#[rustc_box]
Box::new(t)
}
}
impl<T: ?Sized> Deref for Box<T> {
type Target = T;
fn deref(&self) -> &T {
&**self
}
}
impl<T: ?Sized> DerefMut for Box<T> {
fn deref_mut(&mut self) -> &mut T {
&mut **self
}
}
impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<Box<U>> for Box<T> {}
const GOAL: usize = {
let x = Box::new(5);
let y: Box<[i32]> = Box::new([1, 2, 3]);
*x + y.len()
};
"#,
8,
);
}
#[test]
fn array_and_index() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: u8 = {
let a = [10, 20, 3, 15];
let x: &[u8] = &a;
x[1]
};
"#,
20,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = [1, 2, 3][2];"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = { let a = [1, 2, 3]; let x: &[i32] = &a; x.len() };"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = {
let a = [1, 2, 3];
let x: &[i32] = &a;
let y = &*x;
y.len()
};"#,
3,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: usize = [1, 2, 3, 4, 5].len();"#,
5,
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: [u16; 5] = [1, 2, 3, 4, 5];"#,
1 + (2 << 16) + (3 << 32) + (4 << 48) + (5 << 64),
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: [u16; 5] = [12; 5];"#,
12 + (12 << 16) + (12 << 32) + (12 << 48) + (12 << 64),
);
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const LEN: usize = 4;
const GOAL: u16 = {
let x = [7; LEN];
x[2]
}"#,
7,
);
}
#[test]
fn string() {
check_str(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: &str = "hello";
"#,
"hello",
);
}
#[test]
fn byte_string() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice
const GOAL: u8 = {
let a = b"hello";
let x: &[u8] = a;
x[0]
};
"#,
104,
);
}
#[test]
fn c_string() {
check_number(
r#"
//- minicore: index, slice
#[lang = "CStr"]
pub struct CStr {
inner: [u8]
}
const GOAL: u8 = {
let a = c"hello";
a.inner[0]
};
"#,
104,
);
check_number(
r#"
//- minicore: index, slice
#[lang = "CStr"]
pub struct CStr {
inner: [u8]
}
const GOAL: u8 = {
let a = c"hello";
a.inner[6]
};
"#,
0,
);
}
#[test]
fn consts() {
check_number(
r#"
const F1: i32 = 1;
const F3: i32 = 3 * F2;
const F2: i32 = 2 * F1;
const GOAL: i32 = F3;
"#,
6,
);
check_number(
r#"
const F1: i32 = 2147483647;
const F2: i32 = F1 - 25;
const GOAL: i32 = F2;
"#,
2147483622,
);
check_number(
r#"
const F1: i32 = -2147483648;
const F2: i32 = F1 + 18;
const GOAL: i32 = F2;
"#,
-2147483630,
);
check_number(
r#"
const F1: i32 = 10;
const F2: i32 = F1 - 20;
const GOAL: i32 = F2;
"#,
-10,
);
check_number(
r#"
const F1: i32 = 25;
const F2: i32 = F1 - 25;
const GOAL: i32 = F2;
"#,
0,
);
check_number(
r#"
const A: i32 = -2147483648;
const GOAL: bool = A > 0;
"#,
0,
);
check_number(
r#"
const GOAL: i64 = (-2147483648_i32) as i64;
"#,
-2147483648,
);
}
#[test]
fn statics() {
check_number(
r#"
//- minicore: cell
use core::cell::Cell;
fn f() -> i32 {
static S: Cell<i32> = Cell::new(10);
S.set(S.get() + 1);
S.get()
}
const GOAL: i32 = f() + f() + f();
"#,
36,
);
}
#[test]
fn extern_weak_statics() {
check_number(
r#"
extern "C" {
#[linkage = "extern_weak"]
static __dso_handle: *mut u8;
}
const GOAL: usize = __dso_handle as usize;
"#,
0,
);
}
#[test]
fn from_ne_bytes() {
check_number(
r#"
//- minicore: int_impl
const GOAL: u32 = u32::from_ne_bytes([44, 1, 0, 0]);
"#,
300,
);
}
#[test]
fn enums() {
check_number(
r#"
enum E {
F1 = 1,
F2 = 2 * E::F1 as isize, // Rustc expects an isize here
F3 = 3 * E::F2 as isize,
}
const GOAL: u8 = E::F3 as u8;
"#,
6,
);
check_number(
r#"
enum E { F1 = 1, F2, }
const GOAL: u8 = E::F2 as u8;
"#,
2,
);
check_number(
r#"
enum E { F1, }
const GOAL: u8 = E::F1 as u8;
"#,
0,
);
let (db, file_id) = TestDB::with_single_file(
r#"
enum E { A = 1, B }
const GOAL: E = E::A;
"#,
);
let r = eval_goal(&db, file_id).unwrap();
assert_eq!(try_const_usize(&db, &r), Some(1));
}
#[test]
fn const_loop() {
check_fail(
r#"
const F1: i32 = 1 * F3;
const F3: i32 = 3 * F2;
const F2: i32 = 2 * F1;
const GOAL: i32 = F3;
"#,
|e| e == ConstEvalError::MirLowerError(MirLowerError::Loop),
);
}
#[test]
fn const_transfer_memory() {
check_number(
r#"
//- minicore: slice, index, coerce_unsized, option
const A1: &i32 = &1;
const A2: &i32 = &10;
const A3: [&i32; 3] = [&1, &2, &100];
const A4: (i32, &i32, Option<&i32>) = (1, &1000, Some(&10000));
const GOAL: i32 = *A1 + *A2 + *A3[2] + *A4.1 + *A4.2.unwrap_or(&5);
"#,
11111,
);
}
#[test]
fn anonymous_const_block() {
check_number(
r#"
extern "rust-intrinsic" {
pub fn size_of<T>() -> usize;
}
const fn f<T>() -> usize {
let r = const { size_of::<T>() };
r
}
const GOAL: usize = {
let x = const { 2 + const { 3 } };
let y = f::<i32>();
x + y
};
"#,
9,
);
}
#[test]
fn const_impl_assoc() {
check_number(
r#"
struct U5;
impl U5 {
const VAL: usize = 5;
}
const GOAL: usize = U5::VAL + <U5>::VAL;
"#,
10,
);
}
#[test]
fn const_generic_subst_fn() {
check_number(
r#"
const fn f<const A: usize>(x: usize) -> usize {
A * x + 5
}
const GOAL: usize = f::<2>(3);
"#,
11,
);
check_number(
r#"
fn f<const N: usize>(x: [i32; N]) -> usize {
N
}
trait ArrayExt {
fn f(self) -> usize;
}
impl<T, const N: usize> ArrayExt for [T; N] {
fn g(self) -> usize {
f(self)
}
}
const GOAL: usize = f([1, 2, 5]);
"#,
3,
);
}
#[test]
fn layout_of_type_with_associated_type_field_defined_inside_body() {
check_number(
r#"
trait Tr {
type Ty;
}
struct St<T: Tr>(T::Ty);
const GOAL: i64 = {
// if we move `St2` out of body, the test will fail, as we don't see the impl anymore. That
// case will probably be rejected by rustc in some later edition, but we should support this
// case.
struct St2;
impl Tr for St2 {
type Ty = i64;
}
struct Goal(St<St2>);
let x = Goal(St(5));
x.0.0
};
"#,
5,
);
}
#[test]
fn const_generic_subst_assoc_const_impl() {
check_number(
r#"
struct Adder<const N: usize, const M: usize>;
impl<const N: usize, const M: usize> Adder<N, M> {
const VAL: usize = N + M;
}
const GOAL: usize = Adder::<2, 3>::VAL;
"#,
5,
);
}
#[test]
fn associated_types() {
check_number(
r#"
trait Tr {
type Item;
fn get_item(&self) -> Self::Item;
}
struct X(i32);
struct Y(i32);
impl Tr for X {
type Item = Y;
fn get_item(&self) -> Self::Item {
Y(self.0 + 2)
}
}
fn my_get_item<T: Tr>(x: T) -> <T as Tr>::Item {
x.get_item()
}
const GOAL: i32 = my_get_item(X(3)).0;
"#,
5,
);
}
#[test]
fn const_trait_assoc() {
check_number(
r#"
struct U0;
trait ToConst {
const VAL: usize;
}
impl ToConst for U0 {
const VAL: usize = 0;
}
impl ToConst for i32 {
const VAL: usize = 32;
}
const GOAL: usize = U0::VAL + i32::VAL;
"#,
32,
);
check_number(
r#"
//- /a/lib.rs crate:a
pub trait ToConst {
const VAL: usize;
}
pub const fn to_const<T: ToConst>() -> usize {
T::VAL
}
//- /main.rs crate:main deps:a
use a::{ToConst, to_const};
struct U0;
impl ToConst for U0 {
const VAL: usize = 5;
}
const GOAL: usize = to_const::<U0>();
"#,
5,
);
check_number(
r#"
//- minicore: size_of, fn
//- /a/lib.rs crate:a
use core::mem::size_of;
pub struct S<T>(T);
impl<T> S<T> {
pub const X: usize = {
let k: T;
let f = || core::mem::size_of::<T>();
f()
};
}
//- /main.rs crate:main deps:a
use a::{S};
trait Tr {
type Ty;
}
impl Tr for i32 {
type Ty = u64;
}
struct K<T: Tr>(<T as Tr>::Ty);
const GOAL: usize = S::<K<i32>>::X;
"#,
8,
);
check_number(
r#"
struct S<T>(*mut T);
trait MySized: Sized {
const SIZE: S<Self> = S(1 as *mut Self);
}
impl MySized for i32 {
const SIZE: S<i32> = S(10 as *mut i32);
}
impl MySized for i64 {
}
const fn f<T: MySized>() -> usize {
T::SIZE.0 as usize
}
const GOAL: usize = f::<i32>() + f::<i64>() * 2;
"#,
12,
);
}
#[test]
fn exec_limits() {
if skip_slow_tests() {
return;
}
check_fail(
r#"
const GOAL: usize = loop {};
"#,
|e| e == ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded),
);
check_fail(
r#"
const fn f(x: i32) -> i32 {
f(x + 1)
}
const GOAL: i32 = f(0);
"#,
|e| e == ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded),
);
// Reasonable code should still work
check_number(
r#"
const fn nth_odd(n: i32) -> i32 {
2 * n - 1
}
const fn f(n: i32) -> i32 {
let sum = 0;
let i = 0;
while i < n {
i = i + 1;
sum = sum + nth_odd(i);
}
sum
}
const GOAL: i32 = f(1000);
"#,
1000 * 1000,
);
}
#[test]
fn memory_limit() {
check_fail(
r#"
extern "Rust" {
#[rustc_allocator]
fn __rust_alloc(size: usize, align: usize) -> *mut u8;
}
const GOAL: u8 = unsafe {
__rust_alloc(30_000_000_000, 1); // 30GB
2
};
"#,
|e| {
e == ConstEvalError::MirEvalError(MirEvalError::Panic(
"Memory allocation of 30000000000 bytes failed".to_owned(),
))
},
);
}
#[test]
fn type_error() {
check_fail(
r#"
const GOAL: u8 = {
let x: u16 = 2;
let y: (u8, u8) = x;
y.0
};
"#,
|e| matches!(e, ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_))),
);
}
#[test]
fn unsized_field() {
check_number(
r#"
//- minicore: coerce_unsized, index, slice, transmute
use core::mem::transmute;
struct Slice([usize]);
struct Slice2(Slice);
impl Slice2 {
fn as_inner(&self) -> &Slice {
&self.0
}
fn as_bytes(&self) -> &[usize] {
&self.as_inner().0
}
}
const GOAL: usize = unsafe {
let x: &[usize] = &[1, 2, 3];
let x: &Slice2 = transmute(x);
let x = x.as_bytes();
x[0] + x[1] + x[2] + x.len() * 100
};
"#,
306,
);
}
#[test]
fn unsized_local() {
check_fail(
r#"
//- minicore: coerce_unsized, index, slice
const fn x() -> SomeUnknownTypeThatDereferenceToSlice {
SomeUnknownTypeThatDereferenceToSlice
}
const GOAL: u16 = {
let y = x();
let z: &[u16] = &y;
z[1]
};
"#,
|e| matches!(e, ConstEvalError::MirLowerError(MirLowerError::UnsizedTemporary(_))),
);
}
#[test]
fn recursive_adt() {
check_fail(
r#"
//- minicore: coerce_unsized, index, slice
pub enum TagTree {
Leaf,
Choice(&'static [TagTree]),
}
const GOAL: TagTree = {
const TAG_TREE: TagTree = TagTree::Choice(&[
{
const VARIANT_TAG_TREE: TagTree = TagTree::Choice(
&[
TagTree::Leaf,
],
);
VARIANT_TAG_TREE
},
]);
TAG_TREE
};
"#,
|e| matches!(e, ConstEvalError::MirEvalError(MirEvalError::StackOverflow)),
);
}