Skip to content

Commit fe7bf99

Browse files
committed
Implement better handling of divergence
Divergence here means that for some reason, the end of a block will not be reached. We tried to model this just using the never type, but that doesn't work fully (e.g. in `let x = { loop {}; "foo" };` x should still have type `&str`); so this introduces a `diverges` flag that the type checker keeps track of, like rustc does.
1 parent d3eb9d8 commit fe7bf99

File tree

7 files changed

+200
-23
lines changed

7 files changed

+200
-23
lines changed

crates/ra_hir_ty/src/infer.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ struct InferenceContext<'a> {
210210
/// closures, but currently this is the only field that will change there,
211211
/// so it doesn't make sense.
212212
return_ty: Ty,
213+
diverges: Diverges,
213214
}
214215

215216
impl<'a> InferenceContext<'a> {
@@ -224,6 +225,7 @@ impl<'a> InferenceContext<'a> {
224225
owner,
225226
body: db.body(owner),
226227
resolver,
228+
diverges: Diverges::Maybe,
227229
}
228230
}
229231

@@ -666,6 +668,44 @@ impl Expectation {
666668
}
667669
}
668670

671+
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
672+
enum Diverges {
673+
Maybe,
674+
Always,
675+
}
676+
677+
impl Diverges {
678+
fn is_always(self) -> bool {
679+
self == Diverges::Always
680+
}
681+
}
682+
683+
impl std::ops::BitAnd for Diverges {
684+
type Output = Self;
685+
fn bitand(self, other: Self) -> Self {
686+
std::cmp::min(self, other)
687+
}
688+
}
689+
690+
impl std::ops::BitOr for Diverges {
691+
type Output = Self;
692+
fn bitor(self, other: Self) -> Self {
693+
std::cmp::max(self, other)
694+
}
695+
}
696+
697+
impl std::ops::BitAndAssign for Diverges {
698+
fn bitand_assign(&mut self, other: Self) {
699+
*self = *self & other;
700+
}
701+
}
702+
703+
impl std::ops::BitOrAssign for Diverges {
704+
fn bitor_assign(&mut self, other: Self) {
705+
*self = *self | other;
706+
}
707+
}
708+
669709
mod diagnostics {
670710
use hir_def::{expr::ExprId, FunctionId};
671711
use hir_expand::diagnostics::DiagnosticSink;

crates/ra_hir_ty/src/infer/expr.rs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Type inference for expressions.
22
33
use std::iter::{repeat, repeat_with};
4-
use std::sync::Arc;
4+
use std::{mem, sync::Arc};
55

66
use hir_def::{
77
builtin_type::Signedness,
@@ -21,11 +21,15 @@ use crate::{
2121
Ty, TypeCtor, Uncertain,
2222
};
2323

24-
use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch};
24+
use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, Diverges};
2525

2626
impl<'a> InferenceContext<'a> {
2727
pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
2828
let ty = self.infer_expr_inner(tgt_expr, expected);
29+
if ty.is_never() {
30+
// Any expression that produces a value of type `!` must have diverged
31+
self.diverges = Diverges::Always;
32+
}
2933
let could_unify = self.unify(&ty, &expected.ty);
3034
if !could_unify {
3135
self.result.type_mismatches.insert(
@@ -64,11 +68,18 @@ impl<'a> InferenceContext<'a> {
6468
// if let is desugared to match, so this is always simple if
6569
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
6670

71+
let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
72+
let mut both_arms_diverge = Diverges::Always;
73+
6774
let then_ty = self.infer_expr_inner(*then_branch, &expected);
75+
both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe);
6876
let else_ty = match else_branch {
6977
Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
7078
None => Ty::unit(),
7179
};
80+
both_arms_diverge &= self.diverges;
81+
82+
self.diverges = condition_diverges | both_arms_diverge;
7283

7384
self.coerce_merge_branch(&then_ty, &else_ty)
7485
}
@@ -132,10 +143,12 @@ impl<'a> InferenceContext<'a> {
132143
// infer the body.
133144
self.coerce(&closure_ty, &expected.ty);
134145

135-
let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone());
146+
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
147+
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
136148

137149
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
138150

151+
self.diverges = prev_diverges;
139152
self.return_ty = prev_ret_ty;
140153

141154
closure_ty
@@ -165,7 +178,11 @@ impl<'a> InferenceContext<'a> {
165178
self.table.new_type_var()
166179
};
167180

181+
let matchee_diverges = self.diverges;
182+
let mut all_arms_diverge = Diverges::Always;
183+
168184
for arm in arms {
185+
self.diverges = Diverges::Maybe;
169186
let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
170187
if let Some(guard_expr) = arm.guard {
171188
self.infer_expr(
@@ -175,9 +192,12 @@ impl<'a> InferenceContext<'a> {
175192
}
176193

177194
let arm_ty = self.infer_expr_inner(arm.expr, &expected);
195+
all_arms_diverge &= self.diverges;
178196
result_ty = self.coerce_merge_branch(&result_ty, &arm_ty);
179197
}
180198

199+
self.diverges = matchee_diverges | all_arms_diverge;
200+
181201
result_ty
182202
}
183203
Expr::Path(p) => {
@@ -522,7 +542,6 @@ impl<'a> InferenceContext<'a> {
522542
tail: Option<ExprId>,
523543
expected: &Expectation,
524544
) -> Ty {
525-
let mut diverges = false;
526545
for stmt in statements {
527546
match stmt {
528547
Statement::Let { pat, type_ref, initializer } => {
@@ -544,24 +563,30 @@ impl<'a> InferenceContext<'a> {
544563
self.infer_pat(*pat, &ty, BindingMode::default());
545564
}
546565
Statement::Expr(expr) => {
547-
if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) {
548-
diverges = true;
549-
}
566+
self.infer_expr(*expr, &Expectation::none());
550567
}
551568
}
552569
}
553570

554571
let ty = if let Some(expr) = tail {
555572
self.infer_expr_coerce(expr, expected)
556573
} else {
557-
self.coerce(&Ty::unit(), expected.coercion_target());
558-
Ty::unit()
574+
// Citing rustc: if there is no explicit tail expression,
575+
// that is typically equivalent to a tail expression
576+
// of `()` -- except if the block diverges. In that
577+
// case, there is no value supplied from the tail
578+
// expression (assuming there are no other breaks,
579+
// this implies that the type of the block will be
580+
// `!`).
581+
if self.diverges.is_always() {
582+
// we don't even make an attempt at coercion
583+
self.table.new_maybe_never_type_var()
584+
} else {
585+
self.coerce(&Ty::unit(), expected.coercion_target());
586+
Ty::unit()
587+
}
559588
};
560-
if diverges {
561-
Ty::simple(TypeCtor::Never)
562-
} else {
563-
ty
564-
}
589+
ty
565590
}
566591

567592
fn infer_method_call(

crates/ra_hir_ty/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,13 @@ impl Ty {
730730
}
731731
}
732732

733+
pub fn is_never(&self) -> bool {
734+
match self {
735+
Ty::Apply(ApplicationTy { ctor: TypeCtor::Never, .. }) => true,
736+
_ => false,
737+
}
738+
}
739+
733740
/// If this is a `dyn Trait` type, this returns the `Trait` part.
734741
pub fn dyn_trait_ref(&self) -> Option<&TraitRef> {
735742
match self {

crates/ra_hir_ty/src/tests/coercion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ fn foo() -> u32 {
384384
}
385385
"#, true),
386386
@r###"
387-
17..40 '{ ...own; }': !
387+
17..40 '{ ...own; }': u32
388388
23..37 'return unknown': !
389389
30..37 'unknown': u32
390390
"###
@@ -514,7 +514,7 @@ fn foo() {
514514
27..103 '{ ... }': &u32
515515
37..82 'if tru... }': ()
516516
40..44 'true': bool
517-
45..82 '{ ... }': !
517+
45..82 '{ ... }': ()
518518
59..71 'return &1u32': !
519519
66..71 '&1u32': &u32
520520
67..71 '1u32': u32

crates/ra_hir_ty/src/tests/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ fn spam() {
197197
!0..6 '1isize': isize
198198
!0..6 '1isize': isize
199199
!0..6 '1isize': isize
200-
54..457 '{ ...!(); }': !
200+
54..457 '{ ...!(); }': ()
201201
88..109 'spam!(...am!())': {unknown}
202202
115..134 'for _ ...!() {}': ()
203203
119..120 '_': {unknown}

crates/ra_hir_ty/src/tests/never_type.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use super::type_at;
1+
use insta::assert_snapshot;
2+
3+
use super::{infer_with_mismatches, type_at};
24

35
#[test]
46
fn infer_never1() {
@@ -261,3 +263,106 @@ fn test(a: i32) {
261263
);
262264
assert_eq!(t, "f64");
263265
}
266+
267+
#[test]
268+
fn diverging_expression_1() {
269+
let t = infer_with_mismatches(
270+
r#"
271+
//- /main.rs
272+
fn test1() {
273+
let x: u32 = return;
274+
}
275+
fn test2() {
276+
let x: u32 = { return; };
277+
}
278+
fn test3() {
279+
let x: u32 = loop {};
280+
}
281+
fn test4() {
282+
let x: u32 = { loop {} };
283+
}
284+
fn test5() {
285+
let x: u32 = { if true { loop {}; } else { loop {}; } };
286+
}
287+
fn test6() {
288+
let x: u32 = { let y: u32 = { loop {}; }; };
289+
}
290+
"#,
291+
true,
292+
);
293+
assert_snapshot!(t, @r###"
294+
25..53 '{ ...urn; }': ()
295+
35..36 'x': u32
296+
44..50 'return': !
297+
65..98 '{ ...; }; }': ()
298+
75..76 'x': u32
299+
84..95 '{ return; }': u32
300+
86..92 'return': !
301+
110..139 '{ ... {}; }': ()
302+
120..121 'x': u32
303+
129..136 'loop {}': !
304+
134..136 '{}': ()
305+
151..184 '{ ...} }; }': ()
306+
161..162 'x': u32
307+
170..181 '{ loop {} }': u32
308+
172..179 'loop {}': !
309+
177..179 '{}': ()
310+
196..260 '{ ...} }; }': ()
311+
206..207 'x': u32
312+
215..257 '{ if t...}; } }': u32
313+
217..255 'if tru... {}; }': u32
314+
220..224 'true': bool
315+
225..237 '{ loop {}; }': u32
316+
227..234 'loop {}': !
317+
232..234 '{}': ()
318+
243..255 '{ loop {}; }': u32
319+
245..252 'loop {}': !
320+
250..252 '{}': ()
321+
272..324 '{ ...; }; }': ()
322+
282..283 'x': u32
323+
291..321 '{ let ...; }; }': u32
324+
297..298 'y': u32
325+
306..318 '{ loop {}; }': u32
326+
308..315 'loop {}': !
327+
313..315 '{}': ()
328+
"###);
329+
}
330+
331+
#[test]
332+
fn diverging_expression_2() {
333+
let t = infer_with_mismatches(
334+
r#"
335+
//- /main.rs
336+
fn test1() {
337+
// should give type mismatch
338+
let x: u32 = { loop {}; "foo" };
339+
}
340+
"#,
341+
true,
342+
);
343+
assert_snapshot!(t, @r###"
344+
25..98 '{ ..." }; }': ()
345+
68..69 'x': u32
346+
77..95 '{ loop...foo" }': &str
347+
79..86 'loop {}': !
348+
84..86 '{}': ()
349+
88..93 '"foo"': &str
350+
77..95: expected u32, got &str
351+
88..93: expected u32, got &str
352+
"###);
353+
}
354+
355+
#[test]
356+
fn diverging_expression_3_break() {
357+
let t = infer_with_mismatches(
358+
r#"
359+
//- /main.rs
360+
fn test1() {
361+
// should give type mismatch
362+
let x: u32 = { loop { break; } };
363+
}
364+
"#,
365+
true,
366+
);
367+
assert_snapshot!(t, @r###""###);
368+
}

crates/ra_hir_ty/src/tests/simple.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ fn test(a: u32, b: isize, c: !, d: &str) {
179179
17..18 'b': isize
180180
27..28 'c': !
181181
33..34 'd': &str
182-
42..121 '{ ...f32; }': !
182+
42..121 '{ ...f32; }': ()
183183
48..49 'a': u32
184184
55..56 'b': isize
185185
62..63 'c': !
@@ -935,7 +935,7 @@ fn foo() {
935935
29..33 'true': bool
936936
34..51 '{ ... }': i32
937937
44..45 '1': i32
938-
57..80 '{ ... }': !
938+
57..80 '{ ... }': i32
939939
67..73 'return': !
940940
90..93 '_x2': i32
941941
96..149 'if tru... }': i32
@@ -951,7 +951,7 @@ fn foo() {
951951
186..190 'true': bool
952952
194..195 '3': i32
953953
205..206 '_': bool
954-
210..241 '{ ... }': !
954+
210..241 '{ ... }': i32
955955
224..230 'return': !
956956
257..260 '_x4': i32
957957
263..320 'match ... }': i32
@@ -1687,7 +1687,7 @@ fn foo() -> u32 {
16871687
17..59 '{ ...; }; }': ()
16881688
27..28 'x': || -> usize
16891689
31..56 '|| -> ...n 1; }': || -> usize
1690-
43..56 '{ return 1; }': !
1690+
43..56 '{ return 1; }': usize
16911691
45..53 'return 1': !
16921692
52..53 '1': usize
16931693
"###
@@ -1706,7 +1706,7 @@ fn foo() -> u32 {
17061706
17..48 '{ ...; }; }': ()
17071707
27..28 'x': || -> ()
17081708
31..45 '|| { return; }': || -> ()
1709-
34..45 '{ return; }': !
1709+
34..45 '{ return; }': ()
17101710
36..42 'return': !
17111711
"###
17121712
);

0 commit comments

Comments
 (0)