9 mod display_source_code;
11 use std::{env, sync::Arc};
13 use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt};
14 use expect_test::Expect;
16 body::{BodySourceMap, SyntheticSyntax},
17 child_by_source::ChildBySource,
19 item_scope::ItemScope,
22 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
24 use hir_expand::{db::AstDatabase, InFile};
25 use once_cell::race::OnceBool;
29 ast::{self, AstNode, NameOwner},
32 use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
33 use tracing_tree::HierarchicalLayer;
36 db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty,
39 // These tests compare the inference results for all expressions in a file
40 // against snapshots of the expected results using expect. Use
41 // `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
43 fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
44 static ENABLE: OnceBool = OnceBool::new();
45 if !ENABLE.get_or_init(|| env::var("CHALK_DEBUG").is_ok()) {
49 let filter = EnvFilter::from_env("CHALK_DEBUG");
50 let layer = HierarchicalLayer::default()
51 .with_indent_lines(true)
53 .with_indent_amount(2)
54 .with_writer(std::io::stderr);
55 let subscriber = Registry::default().with(filter).with(layer);
56 Some(tracing::subscriber::set_default(subscriber))
59 fn check_types(ra_fixture: &str) {
60 check_types_impl(ra_fixture, false)
63 fn check_types_source_code(ra_fixture: &str) {
64 check_types_impl(ra_fixture, true)
67 fn check_types_impl(ra_fixture: &str, display_source: bool) {
68 let _tracing = setup_tracing();
69 let db = TestDB::with_files(ra_fixture);
70 let mut checked_one = false;
71 for (file_id, annotations) in db.extract_annotations() {
72 for (range, expected) in annotations {
73 let ty = type_at_range(&db, FileRange { file_id, range });
74 let actual = if display_source {
75 let module = db.module_for_file(file_id);
76 ty.display_source_code(&db, module).unwrap()
78 ty.display_test(&db).to_string()
80 assert_eq!(expected, actual);
84 assert!(checked_one, "no `//^` annotations found");
87 fn type_at_range(db: &TestDB, pos: FileRange) -> Ty {
88 let file = db.parse(pos.file_id).ok().unwrap();
89 let expr = algo::find_node_at_range::<ast::Expr>(file.syntax(), pos.range).unwrap();
90 let fn_def = expr.syntax().ancestors().find_map(ast::Fn::cast).unwrap();
91 let module = db.module_for_file(pos.file_id);
92 let func = *module.child_by_source(db)[keys::FUNCTION]
93 .get(&InFile::new(pos.file_id.into(), fn_def))
96 let (_body, source_map) = db.body_with_source_map(func.into());
97 if let Some(expr_id) = source_map.node_expr(InFile::new(pos.file_id.into(), &expr)) {
98 let infer = db.infer(func.into());
99 return infer[expr_id].clone();
101 panic!("Can't find expression")
104 fn infer(ra_fixture: &str) -> String {
105 infer_with_mismatches(ra_fixture, false)
108 fn infer_with_mismatches(content: &str, include_mismatches: bool) -> String {
109 let _tracing = setup_tracing();
110 let (db, file_id) = TestDB::with_single_file(content);
112 let mut buf = String::new();
114 let mut infer_def = |inference_result: Arc<InferenceResult>,
115 body_source_map: Arc<BodySourceMap>| {
116 let mut types: Vec<(InFile<SyntaxNode>, &Ty)> = Vec::new();
117 let mut mismatches: Vec<(InFile<SyntaxNode>, &TypeMismatch)> = Vec::new();
119 for (pat, ty) in inference_result.type_of_pat.iter() {
120 let syntax_ptr = match body_source_map.pat_syntax(pat) {
122 let root = db.parse_or_expand(sp.file_id).unwrap();
125 |it| it.to_node(&root).syntax().clone(),
126 |it| it.to_node(&root).syntax().clone(),
130 Err(SyntheticSyntax) => continue,
132 types.push((syntax_ptr, ty));
135 for (expr, ty) in inference_result.type_of_expr.iter() {
136 let node = match body_source_map.expr_syntax(expr) {
138 let root = db.parse_or_expand(sp.file_id).unwrap();
139 sp.map(|ptr| ptr.to_node(&root).syntax().clone())
141 Err(SyntheticSyntax) => continue,
143 types.push((node.clone(), ty));
144 if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr) {
145 mismatches.push((node, mismatch));
149 // sort ranges for consistency
150 types.sort_by_key(|(node, _)| {
151 let range = node.value.text_range();
152 (range.start(), range.end())
154 for (node, ty) in &types {
155 let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.value.clone()) {
156 (self_param.name().unwrap().syntax().text_range(), "self".to_string())
158 (node.value.text_range(), node.value.text().to_string().replace("\n", " "))
160 let macro_prefix = if node.file_id != file_id.into() { "!" } else { "" };
170 if include_mismatches {
171 mismatches.sort_by_key(|(node, _)| {
172 let range = node.value.text_range();
173 (range.start(), range.end())
175 for (src_ptr, mismatch) in &mismatches {
176 let range = src_ptr.value.text_range();
177 let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" };
180 "{}{:?}: expected {}, got {}\n",
183 mismatch.expected.display_test(&db),
184 mismatch.actual.display_test(&db),
190 let module = db.module_for_file(file_id);
191 let def_map = module.def_map(&db);
193 let mut defs: Vec<DefWithBodyId> = Vec::new();
194 visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
195 defs.sort_by_key(|def| match def {
196 DefWithBodyId::FunctionId(it) => {
197 let loc = it.lookup(&db);
198 let tree = db.item_tree(loc.id.file_id);
199 tree.source(&db, loc.id).syntax().text_range().start()
201 DefWithBodyId::ConstId(it) => {
202 let loc = it.lookup(&db);
203 let tree = db.item_tree(loc.id.file_id);
204 tree.source(&db, loc.id).syntax().text_range().start()
206 DefWithBodyId::StaticId(it) => {
207 let loc = it.lookup(&db);
208 let tree = db.item_tree(loc.id.file_id);
209 tree.source(&db, loc.id).syntax().text_range().start()
213 let (_body, source_map) = db.body_with_source_map(def);
214 let infer = db.infer(def);
215 infer_def(infer, source_map);
218 buf.truncate(buf.trim_end().len());
224 crate_def_map: &DefMap,
225 module_id: LocalModuleId,
226 cb: &mut dyn FnMut(DefWithBodyId),
228 visit_scope(db, crate_def_map, &crate_def_map[module_id].scope, cb);
229 for impl_id in crate_def_map[module_id].scope.impls() {
230 let impl_data = db.impl_data(impl_id);
231 for &item in impl_data.items.iter() {
233 AssocItemId::FunctionId(it) => {
236 let body = db.body(def);
237 visit_scope(db, crate_def_map, &body.item_scope, cb);
239 AssocItemId::ConstId(it) => {
242 let body = db.body(def);
243 visit_scope(db, crate_def_map, &body.item_scope, cb);
245 AssocItemId::TypeAliasId(_) => (),
252 crate_def_map: &DefMap,
254 cb: &mut dyn FnMut(DefWithBodyId),
256 for decl in scope.declarations() {
258 ModuleDefId::FunctionId(it) => {
261 let body = db.body(def);
262 visit_scope(db, crate_def_map, &body.item_scope, cb);
264 ModuleDefId::ConstId(it) => {
267 let body = db.body(def);
268 visit_scope(db, crate_def_map, &body.item_scope, cb);
270 ModuleDefId::StaticId(it) => {
273 let body = db.body(def);
274 visit_scope(db, crate_def_map, &body.item_scope, cb);
276 ModuleDefId::TraitId(it) => {
277 let trait_data = db.trait_data(it);
278 for &(_, item) in trait_data.items.iter() {
280 AssocItemId::FunctionId(it) => cb(it.into()),
281 AssocItemId::ConstId(it) => cb(it.into()),
282 AssocItemId::TypeAliasId(_) => (),
286 ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb),
293 fn ellipsize(mut text: String, max_len: usize) -> String {
294 if text.len() <= max_len {
297 let ellipsis = "...";
298 let e_len = ellipsis.len();
299 let mut prefix_len = (max_len - e_len) / 2;
300 while !text.is_char_boundary(prefix_len) {
303 let mut suffix_len = max_len - e_len - prefix_len;
304 while !text.is_char_boundary(text.len() - suffix_len) {
307 text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
312 fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
313 let (mut db, pos) = TestDB::with_position(
322 let events = db.log_executed(|| {
323 let module = db.module_for_file(pos.file_id);
324 let crate_def_map = module.def_map(&db);
325 visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
329 assert!(format!("{:?}", events).contains("infer"))
341 db.set_file_text(pos.file_id, Arc::new(new_text));
344 let events = db.log_executed(|| {
345 let module = db.module_for_file(pos.file_id);
346 let crate_def_map = db.crate_def_map(module.krate);
347 visit_module(&db, &crate_def_map, module.local_id, &mut |def| {
351 assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events)
355 fn check_infer(ra_fixture: &str, expect: Expect) {
356 let mut actual = infer(ra_fixture);
358 expect.assert_eq(&actual);
361 fn check_infer_with_mismatches(ra_fixture: &str, expect: Expect) {
362 let mut actual = infer_with_mismatches(ra_fixture, true);
364 expect.assert_eq(&actual);