Skip to content

Commit cbc0d20

Browse files
Merge #9239
9239: fix: Fix coercion in match with expected type r=flodiebold a=flodiebold Plus add infrastructure to test type mismatches without expect. CC #8961 Co-authored-by: Florian Diebold <[email protected]>
2 parents 7bbb3e3 + 491b2ac commit cbc0d20

File tree

6 files changed

+182
-55
lines changed

6 files changed

+182
-55
lines changed

crates/base_db/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct FilePosition {
4242
pub offset: TextSize,
4343
}
4444

45-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
45+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
4646
pub struct FileRange {
4747
pub file_id: FileId,
4848
pub range: TextRange,

crates/hir_ty/src/infer.rs

+28
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,34 @@ impl Expectation {
761761
Expectation::RValueLikeUnsized(_) | Expectation::None => None,
762762
}
763763
}
764+
765+
/// Comment copied from rustc:
766+
/// Disregard "castable to" expectations because they
767+
/// can lead us astray. Consider for example `if cond
768+
/// {22} else {c} as u8` -- if we propagate the
769+
/// "castable to u8" constraint to 22, it will pick the
770+
/// type 22u8, which is overly constrained (c might not
771+
/// be a u8). In effect, the problem is that the
772+
/// "castable to" expectation is not the tightest thing
773+
/// we can say, so we want to drop it in this case.
774+
/// The tightest thing we can say is "must unify with
775+
/// else branch". Note that in the case of a "has type"
776+
/// constraint, this limitation does not hold.
777+
///
778+
/// If the expected type is just a type variable, then don't use
779+
/// an expected type. Otherwise, we might write parts of the type
780+
/// when checking the 'then' block which are incompatible with the
781+
/// 'else' branch.
782+
fn adjust_for_branches(&self, table: &mut unify::InferenceTable) -> Expectation {
783+
match self {
784+
Expectation::HasType(ety) => {
785+
let ety = table.resolve_ty_shallow(&ety);
786+
if !ety.is_ty_var() { Expectation::HasType(ety) } else { Expectation::None }
787+
}
788+
Expectation::RValueLikeUnsized(ety) => Expectation::RValueLikeUnsized(ety.clone()),
789+
_ => Expectation::None,
790+
}
791+
}
764792
}
765793

766794
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]

crates/hir_ty/src/infer/expr.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -337,10 +337,15 @@ impl<'a> InferenceContext<'a> {
337337
Expr::Match { expr, arms } => {
338338
let input_ty = self.infer_expr(*expr, &Expectation::none());
339339

340+
let expected = expected.adjust_for_branches(&mut self.table);
341+
340342
let mut result_ty = if arms.is_empty() {
341343
TyKind::Never.intern(&Interner)
342344
} else {
343-
self.table.new_type_var()
345+
match &expected {
346+
Expectation::HasType(ty) => ty.clone(),
347+
_ => self.table.new_type_var(),
348+
}
344349
};
345350

346351
let matchee_diverges = self.diverges;

crates/hir_ty/src/tests.rs

+99-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod macros;
99
mod display_source_code;
1010
mod incremental;
1111

12-
use std::{env, sync::Arc};
12+
use std::{collections::HashMap, env, sync::Arc};
1313

1414
use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt};
1515
use expect_test::Expect;
@@ -83,9 +83,107 @@ fn check_types_impl(ra_fixture: &str, display_source: bool) {
8383
checked_one = true;
8484
}
8585
}
86+
8687
assert!(checked_one, "no `//^` annotations found");
8788
}
8889

90+
fn check_no_mismatches(ra_fixture: &str) {
91+
check_mismatches_impl(ra_fixture, true)
92+
}
93+
94+
#[allow(unused)]
95+
fn check_mismatches(ra_fixture: &str) {
96+
check_mismatches_impl(ra_fixture, false)
97+
}
98+
99+
fn check_mismatches_impl(ra_fixture: &str, allow_none: bool) {
100+
let _tracing = setup_tracing();
101+
let (db, file_id) = TestDB::with_single_file(ra_fixture);
102+
let module = db.module_for_file(file_id);
103+
let def_map = module.def_map(&db);
104+
105+
let mut defs: Vec<DefWithBodyId> = Vec::new();
106+
visit_module(&db, &def_map, module.local_id, &mut |it| defs.push(it));
107+
defs.sort_by_key(|def| match def {
108+
DefWithBodyId::FunctionId(it) => {
109+
let loc = it.lookup(&db);
110+
loc.source(&db).value.syntax().text_range().start()
111+
}
112+
DefWithBodyId::ConstId(it) => {
113+
let loc = it.lookup(&db);
114+
loc.source(&db).value.syntax().text_range().start()
115+
}
116+
DefWithBodyId::StaticId(it) => {
117+
let loc = it.lookup(&db);
118+
loc.source(&db).value.syntax().text_range().start()
119+
}
120+
});
121+
let mut mismatches = HashMap::new();
122+
let mut push_mismatch = |src_ptr: InFile<SyntaxNode>, mismatch: TypeMismatch| {
123+
let range = src_ptr.value.text_range();
124+
if src_ptr.file_id.call_node(&db).is_some() {
125+
panic!("type mismatch in macro expansion");
126+
}
127+
let file_range = FileRange { file_id: src_ptr.file_id.original_file(&db), range };
128+
let actual = format!("expected {}, got {}", mismatch.expected.display_test(&db),
129+
mismatch.actual.display_test(&db));
130+
mismatches.insert(file_range, actual);
131+
};
132+
for def in defs {
133+
let (_body, body_source_map) = db.body_with_source_map(def);
134+
let inference_result = db.infer(def);
135+
for (pat, mismatch) in inference_result.pat_type_mismatches() {
136+
let syntax_ptr = match body_source_map.pat_syntax(pat) {
137+
Ok(sp) => {
138+
let root = db.parse_or_expand(sp.file_id).unwrap();
139+
sp.map(|ptr| {
140+
ptr.either(
141+
|it| it.to_node(&root).syntax().clone(),
142+
|it| it.to_node(&root).syntax().clone(),
143+
)
144+
})
145+
}
146+
Err(SyntheticSyntax) => continue,
147+
};
148+
push_mismatch(syntax_ptr, mismatch.clone());
149+
}
150+
for (expr, mismatch) in inference_result.expr_type_mismatches() {
151+
let node = match body_source_map.expr_syntax(expr) {
152+
Ok(sp) => {
153+
let root = db.parse_or_expand(sp.file_id).unwrap();
154+
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
155+
}
156+
Err(SyntheticSyntax) => continue,
157+
};
158+
push_mismatch(node, mismatch.clone());
159+
}
160+
}
161+
let mut checked_one = false;
162+
for (file_id, annotations) in db.extract_annotations() {
163+
for (range, expected) in annotations {
164+
let file_range = FileRange { file_id, range };
165+
if let Some(mismatch) = mismatches.remove(&file_range) {
166+
assert_eq!(mismatch, expected);
167+
} else {
168+
assert!(false, "Expected mismatch not encountered: {}\n", expected);
169+
}
170+
checked_one = true;
171+
}
172+
}
173+
let mut buf = String::new();
174+
for (range, mismatch) in mismatches {
175+
format_to!(
176+
buf,
177+
"{:?}: {}\n",
178+
range.range,
179+
mismatch,
180+
);
181+
}
182+
assert!(buf.is_empty(), "Unexpected type mismatches:\n{}", buf);
183+
184+
assert!(checked_one || allow_none, "no `//^` annotations found");
185+
}
186+
89187
fn type_at_range(db: &TestDB, pos: FileRange) -> Ty {
90188
let file = db.parse(pos.file_id).ok().unwrap();
91189
let expr = algo::find_node_at_range::<ast::Expr>(file.syntax(), pos.range).unwrap();

crates/hir_ty/src/tests/coercion.rs

+31-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use expect_test::expect;
22

3-
use super::{check_infer, check_infer_with_mismatches, check_types};
3+
use super::{check_infer, check_infer_with_mismatches, check_no_mismatches, check_types};
44

55
#[test]
66
fn infer_block_expr_type_mismatch() {
@@ -963,7 +963,7 @@ fn test() -> i32 {
963963

964964
#[test]
965965
fn panic_macro() {
966-
check_infer_with_mismatches(
966+
check_no_mismatches(
967967
r#"
968968
mod panic {
969969
#[macro_export]
@@ -991,15 +991,34 @@ fn main() {
991991
panic!()
992992
}
993993
"#,
994-
expect![[r#"
995-
174..185 '{ loop {} }': !
996-
176..183 'loop {}': !
997-
181..183 '{}': ()
998-
!0..24 '$crate...:panic': fn panic() -> !
999-
!0..26 '$crate...anic()': !
1000-
!0..26 '$crate...anic()': !
1001-
!0..28 '$crate...015!()': !
1002-
454..470 '{ ...c!() }': ()
1003-
"#]],
994+
);
995+
}
996+
997+
#[test]
998+
fn coerce_unsize_expected_type() {
999+
check_no_mismatches(
1000+
r#"
1001+
#[lang = "sized"]
1002+
pub trait Sized {}
1003+
#[lang = "unsize"]
1004+
pub trait Unsize<T> {}
1005+
#[lang = "coerce_unsized"]
1006+
pub trait CoerceUnsized<T> {}
1007+
1008+
impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
1009+
1010+
fn main() {
1011+
let foo: &[u32] = &[1, 2];
1012+
let foo: &[u32] = match true {
1013+
true => &[1, 2],
1014+
false => &[1, 2, 3],
1015+
};
1016+
let foo: &[u32] = if true {
1017+
&[1, 2]
1018+
} else {
1019+
&[1, 2, 3]
1020+
};
1021+
}
1022+
"#,
10041023
);
10051024
}

crates/hir_ty/src/tests/patterns.rs

+17-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use expect_test::expect;
22

3-
use super::{check_infer, check_infer_with_mismatches, check_types};
3+
use super::{check_infer, check_infer_with_mismatches, check_types, check_mismatches};
44

55
#[test]
66
fn infer_pattern() {
@@ -518,47 +518,24 @@ fn infer_generics_in_patterns() {
518518

519519
#[test]
520520
fn infer_const_pattern() {
521-
check_infer_with_mismatches(
521+
check_mismatches(
522522
r#"
523-
enum Option<T> { None }
524-
use Option::None;
525-
struct Foo;
526-
const Bar: usize = 1;
527-
528-
fn test() {
529-
let a: Option<u32> = None;
530-
let b: Option<i64> = match a {
531-
None => None,
532-
};
533-
let _: () = match () { Foo => Foo }; // Expected mismatch
534-
let _: () = match () { Bar => Bar }; // Expected mismatch
535-
}
523+
enum Option<T> { None }
524+
use Option::None;
525+
struct Foo;
526+
const Bar: usize = 1;
527+
528+
fn test() {
529+
let a: Option<u32> = None;
530+
let b: Option<i64> = match a {
531+
None => None,
532+
};
533+
let _: () = match () { Foo => () };
534+
// ^^^ expected (), got Foo
535+
let _: () = match () { Bar => () };
536+
// ^^^ expected (), got usize
537+
}
536538
"#,
537-
expect![[r#"
538-
73..74 '1': usize
539-
87..309 '{ ...atch }': ()
540-
97..98 'a': Option<u32>
541-
114..118 'None': Option<u32>
542-
128..129 'b': Option<i64>
543-
145..182 'match ... }': Option<i64>
544-
151..152 'a': Option<u32>
545-
163..167 'None': Option<u32>
546-
171..175 'None': Option<i64>
547-
192..193 '_': ()
548-
200..223 'match ... Foo }': Foo
549-
206..208 '()': ()
550-
211..214 'Foo': Foo
551-
218..221 'Foo': Foo
552-
254..255 '_': ()
553-
262..285 'match ... Bar }': usize
554-
268..270 '()': ()
555-
273..276 'Bar': usize
556-
280..283 'Bar': usize
557-
200..223: expected (), got Foo
558-
211..214: expected (), got Foo
559-
262..285: expected (), got usize
560-
273..276: expected (), got usize
561-
"#]],
562539
);
563540
}
564541

0 commit comments

Comments
 (0)