From f521e4185323699cd5d063b2704367a319583982 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 15 Jun 2021 21:02:40 +0300 Subject: internal: introduce minicore -- a subset of libcore for testing --- crates/base_db/src/fixture.rs | 31 ++++- crates/hir_ty/src/tests/coercion.rs | 47 +++---- crates/rust-analyzer/tests/slow-tests/support.rs | 4 +- crates/test_utils/src/fixture.rs | 157 +++++++++++++++++++++-- crates/test_utils/src/lib.rs | 5 +- crates/test_utils/src/minicore.rs | 69 ++++++++++ 6 files changed, 271 insertions(+), 42 deletions(-) create mode 100644 crates/test_utils/src/minicore.rs diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs index 1b17db102..d56b20b83 100644 --- a/crates/base_db/src/fixture.rs +++ b/crates/base_db/src/fixture.rs @@ -9,8 +9,8 @@ use test_utils::{ use vfs::{file_set::FileSet, VfsPath}; use crate::{ - input::CrateName, Change, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, FileRange, - SourceDatabaseExt, SourceRoot, SourceRootId, + input::CrateName, Change, CrateDisplayName, CrateGraph, CrateId, Edition, Env, FileId, + FilePosition, FileRange, SourceDatabaseExt, SourceRoot, SourceRootId, }; pub const WORKSPACE: SourceRootId = SourceRootId(0); @@ -81,7 +81,7 @@ pub struct ChangeFixture { impl ChangeFixture { pub fn parse(ra_fixture: &str) -> ChangeFixture { - let fixture = Fixture::parse(ra_fixture); + let (mini_core, fixture) = Fixture::parse(ra_fixture); let mut change = Change::new(); let mut files = Vec::new(); @@ -166,6 +166,31 @@ impl ChangeFixture { } } + if let Some(mini_core) = mini_core { + let core_file = file_id; + file_id.0 += 1; + + let mut fs = FileSet::default(); + fs.insert(core_file, VfsPath::new_virtual_path("/sysroot/core/lib.rs".to_string())); + roots.push(SourceRoot::new_library(fs)); + + change.change_file(core_file, Some(Arc::new(mini_core.source_code()))); + + let all_crates = crate_graph.crates_in_topological_order(); + + let core_crate = crate_graph.add_crate_root( + core_file, + Edition::Edition2021, + Some(CrateDisplayName::from_canonical_name("core".to_string())), + CfgOptions::default(), + Env::default(), + Vec::new(), + ); + + for krate in all_crates { + crate_graph.add_dep(krate, CrateName::new("core").unwrap(), core_crate).unwrap(); + } + } roots.push(SourceRoot::new_local(mem::take(&mut file_set))); change.set_roots(roots); change.set_crate_graph(crate_graph); diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs index 4f859fc85..91236e974 100644 --- a/crates/hir_ty/src/tests/coercion.rs +++ b/crates/hir_ty/src/tests/coercion.rs @@ -23,38 +23,29 @@ fn infer_block_expr_type_mismatch() { fn coerce_places() { check_infer( r#" - struct S { a: T } +//- minicore: coerce_unsized +struct S { a: T } - fn f(_: &[T]) -> T { loop {} } - fn g(_: S<&[T]>) -> T { loop {} } +fn f(_: &[T]) -> T { loop {} } +fn g(_: S<&[T]>) -> T { loop {} } - fn gen() -> *mut [T; 2] { loop {} } - fn test1() -> *mut [U] { - gen() - } - - fn test2() { - let arr: &[u8; 1] = &[1]; +fn gen() -> *mut [T; 2] { loop {} } +fn test1() -> *mut [U] { + gen() +} - let a: &[_] = arr; - let b = f(arr); - let c: &[_] = { arr }; - let d = g(S { a: arr }); - let e: [&[_]; 1] = [arr]; - let f: [&[_]; 2] = [arr; 2]; - let g: (&[_], &[_]) = (arr, arr); - } +fn test2() { + let arr: &[u8; 1] = &[1]; - #[lang = "sized"] - pub trait Sized {} - #[lang = "unsize"] - pub trait Unsize {} - #[lang = "coerce_unsized"] - pub trait CoerceUnsized {} - - impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} - impl, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} - "#, + let a: &[_] = arr; + let b = f(arr); + let c: &[_] = { arr }; + let d = g(S { a: arr }); + let e: [&[_]; 1] = [arr]; + let f: [&[_]; 2] = [arr; 2]; + let g: (&[_], &[_]) = (arr, arr); +} +"#, expect![[r#" 30..31 '_': &[T] 44..55 '{ loop {} }': T diff --git a/crates/rust-analyzer/tests/slow-tests/support.rs b/crates/rust-analyzer/tests/slow-tests/support.rs index e22c295f9..260a504e7 100644 --- a/crates/rust-analyzer/tests/slow-tests/support.rs +++ b/crates/rust-analyzer/tests/slow-tests/support.rs @@ -75,7 +75,9 @@ impl<'a> Project<'a> { profile::init_from(crate::PROFILE); }); - for entry in Fixture::parse(self.fixture) { + let (mini_core, fixtures) = Fixture::parse(self.fixture); + assert!(mini_core.is_none()); + for entry in fixtures { let path = tmp_dir.path().join(&entry.path['/'.len_utf8()..]); fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); diff --git a/crates/test_utils/src/fixture.rs b/crates/test_utils/src/fixture.rs index d0bddf7d8..535892f3f 100644 --- a/crates/test_utils/src/fixture.rs +++ b/crates/test_utils/src/fixture.rs @@ -77,6 +77,11 @@ pub struct Fixture { pub introduce_new_source_root: bool, } +pub struct MiniCore { + activated_flags: Vec, + valid_flags: Vec, +} + impl Fixture { /// Parses text which looks like this: /// @@ -86,12 +91,28 @@ impl Fixture { /// line 2 /// //- other meta /// ``` - pub fn parse(ra_fixture: &str) -> Vec { + /// + /// Fixture can also start with a minicore declaration: + /// + /// ``` + /// //- minicore: sized + /// ``` + /// + /// That will include a subset of `libcore` into the fixture, see + /// `minicore.rs` for what's available. + pub fn parse(ra_fixture: &str) -> (Option, Vec) { let fixture = trim_indent(ra_fixture); - + let mut fixture = fixture.as_str(); + let mut mini_core = None; let mut res: Vec = Vec::new(); - let default = if ra_fixture.contains("//-") { None } else { Some("//- /main.rs") }; + if fixture.starts_with("//- minicore:") { + let first_line = fixture.split_inclusive('\n').next().unwrap(); + mini_core = Some(MiniCore::parse(first_line)); + fixture = &fixture[first_line.len()..]; + } + + let default = if fixture.contains("//-") { None } else { Some("//- /main.rs") }; for (ix, line) in default.into_iter().chain(fixture.split_inclusive('\n')).enumerate() { if line.contains("//-") { @@ -113,7 +134,7 @@ impl Fixture { } } - res + (mini_core, res) } //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo @@ -172,6 +193,122 @@ impl Fixture { } } +impl MiniCore { + fn has_flag(&self, flag: &str) -> bool { + self.activated_flags.iter().any(|it| it == flag) + } + + fn assert_valid_flag(&self, flag: &str) { + if !self.valid_flags.iter().any(|it| it == flag) { + panic!("invalid flag: {:?}, valid flags: {:?}", flag, self.valid_flags); + } + } + + fn parse(line: &str) -> MiniCore { + let mut res = MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() }; + + let line = line.strip_prefix("//- minicore:").unwrap().trim(); + for entry in line.split(", ") { + if res.has_flag(entry) { + panic!("duplicate minicore flag: {:?}", entry) + } + res.activated_flags.push(entry.to_string()) + } + + res + } + + /// Strips parts of minicore.rs which are flagged by inactive flags. + /// + /// This is probably over-engineered to support flags dependencies. + pub fn source_code(mut self) -> String { + let mut buf = String::new(); + let raw_mini_core = include_str!("./minicore.rs"); + let mut lines = raw_mini_core.split_inclusive('\n'); + + let mut parsing_flags = false; + let mut implications = Vec::new(); + + // Parse `//!` preamble and extract flags and dependencies. + for line in lines.by_ref() { + let line = match line.strip_prefix("//!") { + Some(it) => it, + None => { + assert!(line.trim().is_empty()); + break; + } + }; + + if parsing_flags { + let (flag, deps) = line.split_once(':').unwrap(); + let flag = flag.trim(); + self.valid_flags.push(flag.to_string()); + for dep in deps.split(", ") { + let dep = dep.trim(); + if !dep.is_empty() { + self.assert_valid_flag(dep); + implications.push((flag, dep)); + } + } + } + + if line.contains("Available flags:") { + parsing_flags = true; + } + } + + for flag in &self.activated_flags { + self.assert_valid_flag(flag); + } + + // Fixed point loop to compute transitive closure of flags. + loop { + let mut changed = false; + for &(u, v) in implications.iter() { + if self.has_flag(u) && !self.has_flag(v) { + self.activated_flags.push(v.to_string()); + changed = true; + } + } + if !changed { + break; + } + } + + let mut curr_region = ""; + for line in lines { + let trimmed = line.trim(); + if let Some(region) = trimmed.strip_prefix("// region:") { + assert_eq!(curr_region, ""); + curr_region = region; + continue; + } + if let Some(region) = trimmed.strip_prefix("// endregion:") { + assert_eq!(curr_region, region); + curr_region = ""; + continue; + } + + let mut flag = curr_region; + if let Some(idx) = trimmed.find("// :") { + flag = &trimmed[idx + "// :".len()..]; + } + + let skip = if flag == "" { + false + } else { + self.assert_valid_flag(flag); + !self.has_flag(flag) + }; + + if !skip { + buf.push_str(line) + } + } + buf + } +} + #[test] #[should_panic] fn parse_fixture_checks_further_indented_metadata() { @@ -189,12 +326,14 @@ fn parse_fixture_checks_further_indented_metadata() { #[test] fn parse_fixture_gets_full_meta() { - let parsed = Fixture::parse( - r" - //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo - mod m; - ", + let (mini_core, parsed) = Fixture::parse( + r#" +//- minicore: coerce_unsized +//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo +mod m; +"#, ); + assert_eq!(mini_core.unwrap().activated_flags, vec!["coerce_unsized".to_string()]); assert_eq!(1, parsed.len()); let meta = &parsed[0]; diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index b2fe25f82..d55bae62a 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -23,7 +23,10 @@ use text_size::{TextRange, TextSize}; pub use dissimilar::diff as __diff; pub use rustc_hash::FxHashMap; -pub use crate::{assert_linear::AssertLinear, fixture::Fixture}; +pub use crate::{ + assert_linear::AssertLinear, + fixture::{Fixture, MiniCore}, +}; pub const CURSOR_MARKER: &str = "$0"; pub const ESCAPED_CURSOR_MARKER: &str = "\\$0"; diff --git a/crates/test_utils/src/minicore.rs b/crates/test_utils/src/minicore.rs new file mode 100644 index 000000000..629c06ed0 --- /dev/null +++ b/crates/test_utils/src/minicore.rs @@ -0,0 +1,69 @@ +//! This is a fixture we use for tests that need lang items. +//! +//! We want to include the minimal subset of core for each test, so this file +//! supports "conditional compilation". Tests use the following syntax to include minicore: +//! +//! //- minicore: flag1, flag2 +//! +//! We then strip all the code marked with other flags. +//! +//! Available flags: +//! sized: +//! coerce_unsized: sized + +pub mod marker { + // region:sized + #[lang = "sized"] + #[fundamental] + #[rustc_specialization_trait] + pub trait Sized {} + + #[lang = "unsize"] + pub trait Unsize {} + // endregion:sized +} + +pub mod ops { + mod unsize { + // region:coerce_unsized + use crate::marker::Unsize; + + #[lang = "coerce_unsized"] + pub trait CoerceUnsized {} + + impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a mut U> for &'a mut T {} + impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b mut T {} + impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*mut U> for &'a mut T {} + impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*const U> for &'a mut T {} + + impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} + impl<'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<*const U> for &'a T {} + + impl, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} + impl, U: ?Sized> CoerceUnsized<*const U> for *mut T {} + impl, U: ?Sized> CoerceUnsized<*const U> for *const T {} + // endregion:coerce_unsized + } +} + +pub mod prelude { + pub mod v1 { + pub use crate::marker::Sized; // :sized + } + + pub mod rust_2015 { + pub use super::v1::*; + } + + pub mod rust_2018 { + pub use super::v1::*; + } + + pub mod rust_2021 { + pub use super::v1::*; + } +} + +#[prelude_import] +#[allow(unused)] +use prelude::v1::*; -- cgit v1.2.3